Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 504f5c3

Browse files
committed
Issue #21491: socketserver: Fix a race condition in child processes reaping.
1 parent af9eb96 commit 504f5c3

2 files changed

Lines changed: 32 additions & 26 deletions

File tree

Lib/socketserver.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -523,35 +523,39 @@ class ForkingMixIn:
523523

524524
def collect_children(self):
525525
"""Internal routine to wait for children that have exited."""
526-
if self.active_children is None: return
526+
if self.active_children is None:
527+
return
528+
529+
# If we're above the max number of children, wait and reap them until
530+
# we go back below threshold. Note that we use waitpid(-1) below to be
531+
# able to collect children in size(<defunct children>) syscalls instead
532+
# of size(<children>): the downside is that this might reap children
533+
# which we didn't spawn, which is why we only resort to this when we're
534+
# above max_children.
527535
while len(self.active_children) >= self.max_children:
528-
# XXX: This will wait for any child process, not just ones
529-
# spawned by this library. This could confuse other
530-
# libraries that expect to be able to wait for their own
531-
# children.
532536
try:
533-
pid, status = os.waitpid(0, 0)
537+
pid, _ = os.waitpid(-1, 0)
538+
self.active_children.discard(pid)
539+
except InterruptedError:
540+
pass
541+
except ChildProcessError:
542+
# we don't have any children, we're done
543+
self.active_children.clear()
534544
except OSError:
535-
pid = None
536-
if pid not in self.active_children: continue
537-
self.active_children.remove(pid)
538-
539-
# XXX: This loop runs more system calls than it ought
540-
# to. There should be a way to put the active_children into a
541-
# process group and then use os.waitpid(-pgid) to wait for any
542-
# of that set, but I couldn't find a way to allocate pgids
543-
# that couldn't collide.
544-
for child in self.active_children:
545+
break
546+
547+
# Now reap all defunct children.
548+
for pid in self.active_children.copy():
545549
try:
546-
pid, status = os.waitpid(child, os.WNOHANG)
550+
pid, _ = os.waitpid(pid, os.WNOHANG)
551+
# if the child hasn't exited yet, pid will be 0 and ignored by
552+
# discard() below
553+
self.active_children.discard(pid)
554+
except ChildProcessError:
555+
# someone else reaped it
556+
self.active_children.discard(pid)
547557
except OSError:
548-
pid = None
549-
if not pid: continue
550-
try:
551-
self.active_children.remove(pid)
552-
except ValueError as e:
553-
raise ValueError('%s. x=%d and list=%r' % (e.message, pid,
554-
self.active_children))
558+
pass
555559

556560
def handle_timeout(self):
557561
"""Wait for zombies after self.timeout seconds of inactivity.
@@ -573,8 +577,8 @@ def process_request(self, request, client_address):
573577
if pid:
574578
# Parent process
575579
if self.active_children is None:
576-
self.active_children = []
577-
self.active_children.append(pid)
580+
self.active_children = set()
581+
self.active_children.add(pid)
578582
self.close_request(request)
579583
return
580584
else:

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Core and Builtins
2727
Library
2828
-------
2929

30+
- Issue #21491: socketserver: Fix a race condition in child processes reaping.
31+
3032
- Issue #21722: The distutils "upload" command now exits with a non-zero
3133
return code when uploading fails. Patch by Martin Dengler.
3234

0 commit comments

Comments
 (0)