From 055a4c29efb67b807aee428ee8deeb8e767cfeaf Mon Sep 17 00:00:00 2001 From: XD Trol Date: Sun, 16 Jan 2022 02:11:21 +0800 Subject: [PATCH] bpo-46391: Library multiprocess leaks named resources. --- Lib/multiprocessing/context.py | 14 ++++++++ Lib/multiprocessing/process.py | 10 ++++-- Lib/test/_test_multiprocessing.py | 35 +++++++++++++++++++ .../2022-01-16-03-09-58.bpo-46391.LZUzDI.rst | 2 ++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-01-16-03-09-58.bpo-46391.LZUzDI.rst diff --git a/Lib/multiprocessing/context.py b/Lib/multiprocessing/context.py index 8d0525d5d62179..49f9d0f899f991 100644 --- a/Lib/multiprocessing/context.py +++ b/Lib/multiprocessing/context.py @@ -223,6 +223,10 @@ class Process(process.BaseProcess): def _Popen(process_obj): return _default_context.get_context().Process._Popen(process_obj) + @staticmethod + def _bootstrap_util(): + return _default_context.get_context().Process._bootstrap_util() + class DefaultContext(BaseContext): Process = Process @@ -283,6 +287,11 @@ def _Popen(process_obj): from .popen_spawn_posix import Popen return Popen(process_obj) + @staticmethod + def _bootstrap_util(): + # Process is spawned, no need to bootstrap util. + pass + class ForkServerProcess(process.BaseProcess): _start_method = 'forkserver' @staticmethod @@ -326,6 +335,11 @@ def _Popen(process_obj): from .popen_spawn_win32 import Popen return Popen(process_obj) + @staticmethod + def _bootstrap_util(): + # Process is spawned, no need to bootstrap util. + pass + class SpawnContext(BaseContext): _name = 'spawn' Process = SpawnProcess diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index 0b2e0b45b2397b..346afad6278245 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -304,8 +304,7 @@ def _bootstrap(self, parent_sentinel=None): if threading._HAVE_THREAD_NATIVE_ID: threading.main_thread()._set_native_id() try: - util._finalizer_registry.clear() - util._run_after_forkers() + self._bootstrap_util() finally: # delay finalization of the old process object until after # _run_after_forkers() is executed @@ -336,6 +335,13 @@ def _bootstrap(self, parent_sentinel=None): return exitcode + @staticmethod + def _bootstrap_util(): + from . import util + util._finalizer_registry.clear() + util._run_after_forkers() + + # # We subclass bytes to avoid accidental transmission of auth keys over network # diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index b2d656ab428975..267bdcfe8cd981 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -5706,6 +5706,41 @@ def test_namespace(self): self.run_worker(self._test_namespace, o) +class TestNamedResource(unittest.TestCase): + + def test_global_named_resource_spawn(self): + # + # Check that global named resources in main module + # will not leak by a subprocess, in spawn context. + # + import tempfile as tf + py = tf.NamedTemporaryFile('w', delete=False) + try: + py.write('''if 1: + import multiprocessing as mp + + ctx = mp.get_context('spawn') + + global_resource = ctx.Semaphore() + + def submain(): pass + + if __name__ == '__main__': + p = ctx.Process(target=submain) + p.start() + p.join() + ''') + py.close() + + p = subprocess.run([sys.executable, py.name], + stderr=subprocess.PIPE, + text=True) + + self.assertNotRegex(p.stderr, 'resource_tracker: There appear to be .* leaked') + finally: + os.unlink(py.name) + + class MiscTestCase(unittest.TestCase): def test__all__(self): # Just make sure names in not_exported are excluded diff --git a/Misc/NEWS.d/next/Library/2022-01-16-03-09-58.bpo-46391.LZUzDI.rst b/Misc/NEWS.d/next/Library/2022-01-16-03-09-58.bpo-46391.LZUzDI.rst new file mode 100644 index 00000000000000..edc67a97232e40 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-16-03-09-58.bpo-46391.LZUzDI.rst @@ -0,0 +1,2 @@ +Fix that Library multiprocess leaks named resources when global named +resources are used in the main module in spawn context.