diff -ur a/Lib/socketserver.py b/Lib/socketserver.py --- a/Lib/socketserver.py 2011-05-26 09:59:19.000000000 +0000 +++ b/Lib/socketserver.py 2011-05-26 12:30:55.000000000 +0000 @@ -133,6 +133,7 @@ import select import sys import os +import errno try: import threading except ImportError: @@ -508,40 +509,39 @@ """Mix-in class to handle each request in a new process.""" timeout = 300 - active_children = None - max_children = 40 + # the PGID the children belong to - dynamic + _group = 0 def collect_children(self): """Internal routine to wait for children that have exited.""" - if self.active_children is None: return - 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 + # To speed-up the waiting phase and avoid waiting for children that + # weren't spawned by this server, children are put in a common process + # group. The simplest way to do that would be to call setpgid() from + # the main process, but we can't change arbitrarily the main process + # group. So, when a child process is created, if there is no current + # process group, its PID is used as new PGID. Otherwise, it's made part + # of the existing process group. + + # check whether there are active children + if self._group == 0: + return + + # wait for children in the process group + while True: try: - self.active_children.remove(pid) - except ValueError as e: - raise ValueError('%s. x=%d and list=%r' % (e.message, pid, - self.active_children)) + pid, status = os.waitpid(-self._group, os.WNOHANG) + if pid == 0: + # no child in the process group exited yet + break + except os.error as e: + if e.errno == errno.ECHILD: + # no child left - mark the group as invalid + self._group = 0 + break + elif e.errno == errno.EINTR: + pass + else: + raise def handle_timeout(self): """Wait for zombies after self.timeout seconds of inactivity. @@ -562,14 +562,29 @@ pid = os.fork() if pid: # Parent process - if self.active_children is None: - self.active_children = [] - self.active_children.append(pid) + # If the process group was invalid, then the child's PID is the new + # PGID, store it. + if self._group == 0: + self._group = pid + # We must also set the child's PGID before returning. + # If we didn't, the following might happen: + # - self._group refers to a valid PGID + # - fork() + # - wait all children: the old self._group (inherited by the child + # when it fork()ed) is now invalid + # - the child calls setpgid with the stale PGID, which fails + try: + os.setpgid(pid, self._group) + except OSError: + pass self.close_request(request) - return else: # Child process. # This must never return, hence os._exit()! + # This process either joins the existing process group, or becomes + # the process group leader (calling setgpid with a PGID of 0 means + # that the new PGID should be set to the current PID). + os.setpgid(0, self._group) try: self.finish_request(request, client_address) self.shutdown_request(request)