]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-127586: multiprocessing.Pool does not properly restore blocked signals...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sun, 29 Dec 2024 19:02:59 +0000 (20:02 +0100)
committerGitHub <noreply@github.com>
Sun, 29 Dec 2024 19:02:59 +0000 (11:02 -0800)
gh-127586: multiprocessing.Pool does not properly restore blocked signals (try 2) (GH-128011)

Correct pthread_sigmask in resource_tracker to restore old signals

Using SIG_UNBLOCK to remove blocked "ignored signals" may accidentally
cause side effects if the calling parent already had said signals
blocked to begin with and did not intend to unblock them when
creating a pool. Use SIG_SETMASK instead with the previous mask of
blocked signals to restore the original blocked set.
(cherry picked from commit aeb9b65aa26444529e4adc7d6e5b0d3dd9889ec2)

Co-authored-by: Stephen Hansen <stephen.paul.hansen@gmail.com>
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
Lib/multiprocessing/resource_tracker.py
Lib/test/_test_multiprocessing.py
Misc/NEWS.d/next/Library/2024-12-03-20-28-08.gh-issue-127586.zgotYF.rst [new file with mode: 0644]

index 79e96ecf3245f478b7c851d623c2dd6c1f6aec2c..23fea295c357a586e063553ff2c9d23abb92915d 100644 (file)
@@ -142,13 +142,14 @@ class ResourceTracker(object):
                 # that can make the child die before it registers signal handlers
                 # for SIGINT and SIGTERM. The mask is unregistered after spawning
                 # the child.
+                prev_sigmask = None
                 try:
                     if _HAVE_SIGMASK:
-                        signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
+                        prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
                     pid = util.spawnv_passfds(exe, args, fds_to_pass)
                 finally:
-                    if _HAVE_SIGMASK:
-                        signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)
+                    if prev_sigmask is not None:
+                        signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask)
             except:
                 os.close(w)
                 raise
index 3b4415b50ae2c273c3e7d82c8125cb1c6f4d82ef..85b7290b5a516cad9f466da4fa47ff7ae0b2cd12 100644 (file)
@@ -5826,6 +5826,27 @@ class TestResourceTracker(unittest.TestCase):
             resource_tracker.register(too_long_name_resource, rtype)
 
 
+    @unittest.skipUnless(hasattr(signal, "pthread_sigmask"), "pthread_sigmask is not available")
+    def test_resource_tracker_blocked_signals(self):
+        #
+        # gh-127586: Check that resource_tracker does not override blocked signals of caller.
+        #
+        from multiprocessing.resource_tracker import ResourceTracker
+        orig_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, set())
+        signals = {signal.SIGTERM, signal.SIGINT, signal.SIGUSR1}
+
+        try:
+            for sig in signals:
+                signal.pthread_sigmask(signal.SIG_SETMASK, {sig})
+                self.assertEqual(signal.pthread_sigmask(signal.SIG_BLOCK, set()), {sig})
+                tracker = ResourceTracker()
+                tracker.ensure_running()
+                self.assertEqual(signal.pthread_sigmask(signal.SIG_BLOCK, set()), {sig})
+                tracker._stop()
+        finally:
+            # restore sigmask to what it was before executing test
+            signal.pthread_sigmask(signal.SIG_SETMASK, orig_sigmask)
+
 class TestSimpleQueue(unittest.TestCase):
 
     @classmethod
diff --git a/Misc/NEWS.d/next/Library/2024-12-03-20-28-08.gh-issue-127586.zgotYF.rst b/Misc/NEWS.d/next/Library/2024-12-03-20-28-08.gh-issue-127586.zgotYF.rst
new file mode 100644 (file)
index 0000000..80217bd
--- /dev/null
@@ -0,0 +1,3 @@
+:class:`multiprocessing.pool.Pool` now properly restores blocked signal handlers
+of the parent thread when creating processes via either *spawn* or
+*forkserver*.