diff options
author | Charles-François Natali <cf.natali@gmail.com> | 2014-06-20 21:49:26 (GMT) |
---|---|---|
committer | Charles-François Natali <cf.natali@gmail.com> | 2014-06-20 21:49:26 (GMT) |
commit | af4db37f2a432c809b19452648f66742a7ccc9e1 (patch) | |
tree | 61a29589a22a7bbce00a976977287ba6b99dc30c /Lib/socketserver.py | |
parent | ccc342d67addf4038cf1babd9b46a6149e45185c (diff) | |
download | cpython-af4db37f2a432c809b19452648f66742a7ccc9e1.zip cpython-af4db37f2a432c809b19452648f66742a7ccc9e1.tar.gz cpython-af4db37f2a432c809b19452648f66742a7ccc9e1.tar.bz2 |
Issue #21491: socketserver: Fix a race condition in child processes reaping.
Diffstat (limited to 'Lib/socketserver.py')
-rw-r--r-- | Lib/socketserver.py | 56 |
1 files changed, 30 insertions, 26 deletions
diff --git a/Lib/socketserver.py b/Lib/socketserver.py index 7c85fbc..b585640 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -539,35 +539,39 @@ 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(<defunct children>) syscalls instead + # of size(<children>): 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) + pid, _ = os.waitpid(-1, 0) + self.active_children.discard(pid) + except InterruptedError: + pass + except ChildProcessError: + # we don't have any children, we're done + self.active_children.clear() except OSError: - 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: + break + + # Now reap all defunct children. + for pid in self.active_children.copy(): try: - pid, status = os.waitpid(child, os.WNOHANG) + 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 ChildProcessError: + # someone else reaped it + self.active_children.discard(pid) except OSError: - pid = None - if not pid: continue - try: - self.active_children.remove(pid) - except ValueError as e: - raise ValueError('%s. x=%d and list=%r' % (e.message, pid, - self.active_children)) + pass def handle_timeout(self): """Wait for zombies after self.timeout seconds of inactivity. @@ -589,8 +593,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) return else: |