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.