]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-90549: Fix leak of global named resources using multiprocessing spawn (GH-30617)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 10 Jun 2022 12:17:11 +0000 (05:17 -0700)
committerGitHub <noreply@github.com>
Fri, 10 Jun 2022 12:17:11 +0000 (05:17 -0700)
Co-authored-by: XD Trol <milestonejxd@gmail.com>
Co-authored-by: Antoine Pitrou <pitrou@free.fr>
(cherry picked from commit 30610d28374f5a9698d456cebf3ae496ac01af51)

Co-authored-by: Leo Trol <milestone.jxd@gmail.com>
Lib/multiprocessing/context.py
Lib/multiprocessing/process.py
Lib/test/_test_multiprocessing.py
Misc/NEWS.d/next/Library/2022-06-07-14-53-46.gh-issue-90549.T4FMKY.rst [new file with mode: 0644]

index 8d0525d5d62179b0ac6d8ca16231f8d428ff44b3..b1960ea296fe208935b706898f3d564b9a598d6e 100644 (file)
@@ -223,6 +223,10 @@ class Process(process.BaseProcess):
     def _Popen(process_obj):
         return _default_context.get_context().Process._Popen(process_obj)
 
+    @staticmethod
+    def _after_fork():
+        return _default_context.get_context().Process._after_fork()
+
 class DefaultContext(BaseContext):
     Process = Process
 
@@ -283,6 +287,11 @@ if sys.platform != 'win32':
             from .popen_spawn_posix import Popen
             return Popen(process_obj)
 
+        @staticmethod
+        def _after_fork():
+            # process is spawned, nothing to do
+            pass
+
     class ForkServerProcess(process.BaseProcess):
         _start_method = 'forkserver'
         @staticmethod
@@ -326,6 +335,11 @@ else:
             from .popen_spawn_win32 import Popen
             return Popen(process_obj)
 
+        @staticmethod
+        def _after_fork():
+            # process is spawned, nothing to do
+            pass
+
     class SpawnContext(BaseContext):
         _name = 'spawn'
         Process = SpawnProcess
index 0b2e0b45b2397be0a672b36b7e4c572b3996cf53..2d5372e6d947c849eedef02b7885acfd25930e12 100644 (file)
@@ -304,8 +304,7 @@ class BaseProcess(object):
             if threading._HAVE_THREAD_NATIVE_ID:
                 threading.main_thread()._set_native_id()
             try:
-                util._finalizer_registry.clear()
-                util._run_after_forkers()
+                self._after_fork()
             finally:
                 # delay finalization of the old process object until after
                 # _run_after_forkers() is executed
@@ -336,6 +335,13 @@ class BaseProcess(object):
 
         return exitcode
 
+    @staticmethod
+    def _after_fork():
+        from . import util
+        util._finalizer_registry.clear()
+        util._run_after_forkers()
+
+
 #
 # We subclass bytes to avoid accidental transmission of auth keys over network
 #
index 04657c08db92c04001aac5a0062e72c5992a44ba..1b55cb31c30304e876f13ab148795d9ac93c509a 100644 (file)
@@ -5,6 +5,7 @@
 import unittest
 import unittest.mock
 import queue as pyqueue
+import textwrap
 import time
 import io
 import itertools
@@ -5699,6 +5700,35 @@ class TestSyncManagerTypes(unittest.TestCase):
         self.run_worker(self._test_namespace, o)
 
 
+class TestNamedResource(unittest.TestCase):
+    def test_global_named_resource_spawn(self):
+        #
+        # gh-90549: Check that global named resources in main module
+        # will not leak by a subprocess, in spawn context.
+        #
+        testfn = os_helper.TESTFN
+        self.addCleanup(os_helper.unlink, testfn)
+        with open(testfn, 'w', encoding='utf-8') as f:
+            f.write(textwrap.dedent('''\
+                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()
+            '''))
+        rc, out, err = test.support.script_helper.assert_python_ok(testfn)
+        # on error, err = 'UserWarning: resource_tracker: There appear to
+        # be 1 leaked semaphore objects to clean up at shutdown'
+        self.assertEqual(err, b'')
+
+
 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-06-07-14-53-46.gh-issue-90549.T4FMKY.rst b/Misc/NEWS.d/next/Library/2022-06-07-14-53-46.gh-issue-90549.T4FMKY.rst
new file mode 100644 (file)
index 0000000..6ebdc39
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a multiprocessing bug where a global named resource (such as a semaphore)
+could leak when a child process is spawned (as opposed to forked).