Skip to content

Commit be7314f

Browse files
miss-islingtonstephen-hansenZeroIntensitygpshead
authored
[3.12] gh-127586: multiprocessing.Pool does not properly restore blocked signals (try 2) (GH-128011) (#128299)
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 aeb9b65) Co-authored-by: Stephen Hansen <[email protected]> Co-authored-by: Peter Bierma <[email protected]> Co-authored-by: Gregory P. Smith <[email protected]>
1 parent 3a726be commit be7314f

File tree

3 files changed

+28
-3
lines changed

3 files changed

+28
-3
lines changed

Lib/multiprocessing/resource_tracker.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,14 @@ def ensure_running(self):
142142
# that can make the child die before it registers signal handlers
143143
# for SIGINT and SIGTERM. The mask is unregistered after spawning
144144
# the child.
145+
prev_sigmask = None
145146
try:
146147
if _HAVE_SIGMASK:
147-
signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
148+
prev_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)
148149
pid = util.spawnv_passfds(exe, args, fds_to_pass)
149150
finally:
150-
if _HAVE_SIGMASK:
151-
signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)
151+
if prev_sigmask is not None:
152+
signal.pthread_sigmask(signal.SIG_SETMASK, prev_sigmask)
152153
except:
153154
os.close(w)
154155
raise

Lib/test/_test_multiprocessing.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5826,6 +5826,27 @@ def test_too_long_name_resource(self):
58265826
resource_tracker.register(too_long_name_resource, rtype)
58275827

58285828

5829+
@unittest.skipUnless(hasattr(signal, "pthread_sigmask"), "pthread_sigmask is not available")
5830+
def test_resource_tracker_blocked_signals(self):
5831+
#
5832+
# gh-127586: Check that resource_tracker does not override blocked signals of caller.
5833+
#
5834+
from multiprocessing.resource_tracker import ResourceTracker
5835+
orig_sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, set())
5836+
signals = {signal.SIGTERM, signal.SIGINT, signal.SIGUSR1}
5837+
5838+
try:
5839+
for sig in signals:
5840+
signal.pthread_sigmask(signal.SIG_SETMASK, {sig})
5841+
self.assertEqual(signal.pthread_sigmask(signal.SIG_BLOCK, set()), {sig})
5842+
tracker = ResourceTracker()
5843+
tracker.ensure_running()
5844+
self.assertEqual(signal.pthread_sigmask(signal.SIG_BLOCK, set()), {sig})
5845+
tracker._stop()
5846+
finally:
5847+
# restore sigmask to what it was before executing test
5848+
signal.pthread_sigmask(signal.SIG_SETMASK, orig_sigmask)
5849+
58295850
class TestSimpleQueue(unittest.TestCase):
58305851

58315852
@classmethod
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:class:`multiprocessing.pool.Pool` now properly restores blocked signal handlers
2+
of the parent thread when creating processes via either *spawn* or
3+
*forkserver*.

0 commit comments

Comments
 (0)