bpo-33613, test_semaphore_tracker_sigint: fix race condition (#7850) · python/cpython@ec74d18

@@ -23,6 +23,9 @@

23232424

__all__ = ['ensure_running', 'register', 'unregister']

252526+

_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask')

27+

_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM)

28+26292730

class SemaphoreTracker(object):

2831

@@ -43,10 +46,16 @@ def ensure_running(self):

4346

with self._lock:

4447

if self._pid is not None:

4548

# semaphore tracker was launched before, is it still running?

46-

pid, status = os.waitpid(self._pid, os.WNOHANG)

47-

if not pid:

48-

# => still alive

49-

return

49+

try:

50+

pid, _ = os.waitpid(self._pid, os.WNOHANG)

51+

except ChildProcessError:

52+

# The process terminated

53+

pass

54+

else:

55+

if not pid:

56+

# => still alive

57+

return

58+5059

# => dead, launch it again

5160

os.close(self._fd)

5261

self._fd = None

@@ -68,7 +77,19 @@ def ensure_running(self):

6877

exe = spawn.get_executable()

6978

args = [exe] + util._args_from_interpreter_flags()

7079

args += ['-c', cmd % r]

71-

pid = util.spawnv_passfds(exe, args, fds_to_pass)

80+

# bpo-33613: Register a signal mask that will block the signals.

81+

# This signal mask will be inherited by the child that is going

82+

# to be spawned and will protect the child from a race condition

83+

# that can make the child die before it registers signal handlers

84+

# for SIGINT and SIGTERM. The mask is unregistered after spawning

85+

# the child.

86+

try:

87+

if _HAVE_SIGMASK:

88+

signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS)

89+

pid = util.spawnv_passfds(exe, args, fds_to_pass)

90+

finally:

91+

if _HAVE_SIGMASK:

92+

signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

7293

except:

7394

os.close(w)

7495

raise

@@ -104,12 +125,13 @@ def _send(self, cmd, name):

104125

unregister = _semaphore_tracker.unregister

105126

getfd = _semaphore_tracker.getfd

106127107-108128

def main(fd):

109129

'''Run semaphore tracker.'''

110130

# protect the process from ^C and "killall python" etc

111131

signal.signal(signal.SIGINT, signal.SIG_IGN)

112132

signal.signal(signal.SIGTERM, signal.SIG_IGN)

133+

if _HAVE_SIGMASK:

134+

signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS)

113135114136

for f in (sys.stdin, sys.stdout):

115137

try: