From 98fbdd682908638b54742fcf5107137170b2e645 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Charles-Fran=C3=A7ois=20Natali?= Date: Fri, 20 Jun 2014 22:03:08 +0100 Subject: [PATCH] Issue #21491: SocketServer: Fix a race condition in child processes reaping. --- Lib/SocketServer.py | 58 +++++++++++++++++++++++---------------------- Misc/NEWS | 2 ++ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/Lib/SocketServer.py b/Lib/SocketServer.py index 26611b74d1..afb8686f73 100644 --- a/Lib/SocketServer.py +++ b/Lib/SocketServer.py @@ -513,35 +513,37 @@ class ForkingMixIn: def collect_children(self): """Internal routine to wait for children that have exited.""" - if self.active_children is None: return + if self.active_children is None: + return + + # If we're above the max number of children, wait and reap them until + # we go back below threshold. Note that we use waitpid(-1) below to be + # able to collect children in size() syscalls instead + # of size(): the downside is that this might reap children + # which we didn't spawn, which is why we only resort to this when we're + # above max_children. while len(self.active_children) >= self.max_children: - # XXX: This will wait for any child process, not just ones - # spawned by this library. This could confuse other - # libraries that expect to be able to wait for their own - # children. - try: - pid, status = os.waitpid(0, 0) - except os.error: - pid = None - if pid not in self.active_children: continue - self.active_children.remove(pid) - - # XXX: This loop runs more system calls than it ought - # to. There should be a way to put the active_children into a - # process group and then use os.waitpid(-pgid) to wait for any - # of that set, but I couldn't find a way to allocate pgids - # that couldn't collide. - for child in self.active_children: try: - pid, status = os.waitpid(child, os.WNOHANG) - except os.error: - pid = None - if not pid: continue + pid, _ = os.waitpid(-1, 0) + self.active_children.discard(pid) + except OSError as e: + if e.errno == errno.ECHILD: + # we don't have any children, we're done + self.active_children.clear() + elif e.errno != errno.EINTR: + break + + # Now reap all defunct children. + for pid in self.active_children.copy(): try: - self.active_children.remove(pid) - except ValueError, e: - raise ValueError('%s. x=%d and list=%r' % (e.message, pid, - self.active_children)) + pid, _ = os.waitpid(pid, os.WNOHANG) + # if the child hasn't exited yet, pid will be 0 and ignored by + # discard() below + self.active_children.discard(pid) + except OSError as e: + if e.errno == errno.ECHILD: + # someone else reaped it + self.active_children.discard(pid) def handle_timeout(self): """Wait for zombies after self.timeout seconds of inactivity. @@ -557,8 +559,8 @@ class ForkingMixIn: if pid: # Parent process if self.active_children is None: - self.active_children = [] - self.active_children.append(pid) + self.active_children = set() + self.active_children.add(pid) self.close_request(request) #close handle in parent process return else: diff --git a/Misc/NEWS b/Misc/NEWS index 9ce00018a4..6cd1bd245a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -29,6 +29,8 @@ Core and Builtins Library ------- +- Issue #21491: SocketServer: Fix a race condition in child processes reaping. + - Issue #21722: The distutils "upload" command now exits with a non-zero return code when uploading fails. Patch by Martin Dengler. -- 2.50.1