bpo-36668: FIX reuse semaphore tracker for child processes (#5172)

Fix the multiprocessing.semaphore_tracker so it is reused by child processes.
This commit is contained in:
Thomas Moreau 2019-04-24 21:45:52 +02:00 committed by Antoine Pitrou
parent 09d434caa2
commit 004b93ea89
3 changed files with 56 additions and 11 deletions

View File

@ -44,20 +44,23 @@ class SemaphoreTracker(object):
This can be run from any process. Usually a child process will use
the semaphore created by its parent.'''
with self._lock:
if self._pid is not None:
if self._fd is not None:
# semaphore tracker was launched before, is it still running?
try:
pid, _ = os.waitpid(self._pid, os.WNOHANG)
except ChildProcessError:
# The process terminated
pass
else:
if not pid:
# => still alive
return
if self._check_alive():
# => still alive
return
# => dead, launch it again
os.close(self._fd)
# Clean-up to avoid dangling processes.
try:
# _pid can be None if this process is a child from another
# python process, which has started the semaphore_tracker.
if self._pid is not None:
os.waitpid(self._pid, 0)
except ChildProcessError:
# The semaphore_tracker has already been terminated.
pass
self._fd = None
self._pid = None
@ -99,6 +102,17 @@ class SemaphoreTracker(object):
finally:
os.close(r)
def _check_alive(self):
'''Check that the pipe has not been closed by sending a probe.'''
try:
# We cannot use send here as it calls ensure_running, creating
# a cycle.
os.write(self._fd, b'PROBE:0\n')
except OSError:
return False
else:
return True
def register(self, name):
'''Register name of semaphore with semaphore tracker.'''
self._send('REGISTER', name)
@ -150,6 +164,8 @@ def main(fd):
cache.add(name)
elif cmd == b'UNREGISTER':
cache.remove(name)
elif cmd == b'PROBE':
pass
else:
raise RuntimeError('unrecognized command %r' % cmd)
except Exception:

View File

@ -4891,6 +4891,34 @@ class TestSemaphoreTracker(unittest.TestCase):
# Uncatchable signal.
self.check_semaphore_tracker_death(signal.SIGKILL, True)
@staticmethod
def _is_semaphore_tracker_reused(conn, pid):
from multiprocessing.semaphore_tracker import _semaphore_tracker
_semaphore_tracker.ensure_running()
# The pid should be None in the child process, expect for the fork
# context. It should not be a new value.
reused = _semaphore_tracker._pid in (None, pid)
reused &= _semaphore_tracker._check_alive()
conn.send(reused)
def test_semaphore_tracker_reused(self):
from multiprocessing.semaphore_tracker import _semaphore_tracker
_semaphore_tracker.ensure_running()
pid = _semaphore_tracker._pid
r, w = multiprocessing.Pipe(duplex=False)
p = multiprocessing.Process(target=self._is_semaphore_tracker_reused,
args=(w, pid))
p.start()
is_semaphore_tracker_reused = r.recv()
# Clean up
p.join()
w.close()
r.close()
self.assertTrue(is_semaphore_tracker_reused)
class TestSimpleQueue(unittest.TestCase):

View File

@ -0,0 +1 @@
Fix the multiprocessing.semaphore_tracker so it is reused by child processes