diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index d97381926d47bc..84294143dc012e 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -476,6 +476,12 @@ class State(object): STARTED = 1 SHUTDOWN = 2 + def __getstate__(self): + return self.value + + def __setstate__(self, state): + self.value = state + # # Mapping from serializer name to Listener and Client types # @@ -731,6 +737,15 @@ def temp(self, /, *args, **kwds): temp.__name__ = typeid setattr(cls, typeid, temp) + def __getstate__(self): + state = vars(self).copy() + state['shutdown'] = state['shutdown']._key + return state + + def __setstate__(self, state): + vars(self).update(state) + self.shutdown = util._finalizer_registry[self.shutdown] + # # Subclass of set which get cleared after a fork # diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index 625981cf47627c..dc0c2eb078b16d 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -81,3 +81,12 @@ def _launch(self, process_obj): def close(self): if self.finalizer is not None: self.finalizer() + + def __getstate__(self): + state = vars(self).copy() + state['finalizer'] = state['finalizer']._key + return state + + def __setstate__(self, state): + vars(self).update(state) + self.finalizer = util._finalizer_registry[self.finalizer] diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py index 9c4098d0fa4f1e..b060af2a2984a5 100644 --- a/Lib/multiprocessing/popen_spawn_win32.py +++ b/Lib/multiprocessing/popen_spawn_win32.py @@ -129,3 +129,12 @@ def terminate(self): def close(self): self.finalizer() + + def __getstate__(self): + state = vars(self).copy() + state['finalizer'] = state['finalizer']._key + return state + + def __setstate__(self, state): + vars(self).update(state) + self.finalizer = util._finalizer_registry[self.finalizer] diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index bb73d9e7cc75e4..49841cd874f166 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -807,6 +807,23 @@ def test_forkserver_sigkill(self): if os.name != 'nt': self.check_forkserver_death(signal.SIGKILL) + def test_process_pickling(self): + if self.TYPE == 'threads': + self.skipTest(f'test not appropriate for {self.TYPE}') + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + proc = self.Process() + proc.start() + # Calling set_spawning_popen with a value other than None + # allows pickling the authentication keys of processes + # (.authkey), which is prevented by default in + # AuthenticationString for security reasons. + multiprocessing.context.set_spawning_popen(0) + serialized_proc = pickle.dumps(proc, protocol=proto) + multiprocessing.context.set_spawning_popen(None) + deserialized_proc = pickle.loads(serialized_proc) + self.assertIsInstance(deserialized_proc, self.Process) + # # @@ -2920,7 +2937,26 @@ def test_mymanager_context_prestarted(self): manager.start() with manager: self.common(manager) - self.assertEqual(manager._process.exitcode, 0) + # bpo-30356: BaseManager._finalize_manager() sends SIGTERM + # to the manager process if it takes longer than 1 second to stop, + # which happens on slow buildbots. + self.assertIn(manager._process.exitcode, (0, -signal.SIGTERM)) + + def test_mymanager_pickling(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + manager = MyManager() + manager.start() + # Calling set_spawning_popen with a value other than None + # allows pickling the authentication keys of processes + # (.authkey), which is prevented by default in + # AuthenticationString for security reasons. + multiprocessing.context.set_spawning_popen(0) + serialized_manager = pickle.dumps(manager, protocol=proto) + multiprocessing.context.set_spawning_popen(None) + deserialized_manager = pickle.loads(serialized_manager) + self.assertIsInstance(deserialized_manager, MyManager) + manager.shutdown() def common(self, manager): foo = manager.Foo() diff --git a/Misc/NEWS.d/next/Library/2022-11-30-11-25-08.bpo-46934.zk8HaW.rst b/Misc/NEWS.d/next/Library/2022-11-30-11-25-08.bpo-46934.zk8HaW.rst new file mode 100644 index 00000000000000..a9387390b0d5cc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-30-11-25-08.bpo-46934.zk8HaW.rst @@ -0,0 +1,3 @@ +Make started :class:`multiprocessing.Process` instances and started +:class:`multiprocessing.managers.BaseManager` instances serialisable. Patch by +Géry Ogam.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: