Issue #21491: SocketServer: Fix a race condition in child processes reaping.

parent 72998186
...@@ -513,35 +513,37 @@ class ForkingMixIn: ...@@ -513,35 +513,37 @@ class ForkingMixIn:
def collect_children(self): def collect_children(self):
"""Internal routine to wait for children that have exited.""" """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: 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: try:
pid, status = os.waitpid(child, os.WNOHANG) pid, _ = os.waitpid(-1, 0)
except os.error: self.active_children.discard(pid)
pid = None except OSError as e:
if not pid: continue 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: try:
self.active_children.remove(pid) pid, _ = os.waitpid(pid, os.WNOHANG)
except ValueError, e: # if the child hasn't exited yet, pid will be 0 and ignored by
raise ValueError('%s. x=%d and list=%r' % (e.message, pid, # discard() below
self.active_children)) 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): def handle_timeout(self):
"""Wait for zombies after self.timeout seconds of inactivity. """Wait for zombies after self.timeout seconds of inactivity.
...@@ -557,8 +559,8 @@ class ForkingMixIn: ...@@ -557,8 +559,8 @@ class ForkingMixIn:
if pid: if pid:
# Parent process # Parent process
if self.active_children is None: if self.active_children is None:
self.active_children = [] self.active_children = set()
self.active_children.append(pid) self.active_children.add(pid)
self.close_request(request) #close handle in parent process self.close_request(request) #close handle in parent process
return return
else: else:
......
...@@ -29,6 +29,8 @@ Core and Builtins ...@@ -29,6 +29,8 @@ Core and Builtins
Library Library
------- -------
- Issue #21491: SocketServer: Fix a race condition in child processes reaping.
- Issue #21722: The distutils "upload" command now exits with a non-zero - Issue #21722: The distutils "upload" command now exits with a non-zero
return code when uploading fails. Patch by Martin Dengler. return code when uploading fails. Patch by Martin Dengler.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment