Fix closes issue #11109 - socketserver.ForkingMixIn leaves zombies, also fails to reap all zombies in one pass.
A new method called service_action is made available in BaseServer, called by serve_forever loop. This useful in cases where Mixins can use it for cleanup action. ForkingMixin class uses service_action to collect the zombie child processes. Initial Patch by Justin Wark.
This commit is contained in:
parent
dac9acedfd
commit
5e826e8a1b
|
@ -153,8 +153,21 @@ Server Objects
|
||||||
.. method:: BaseServer.serve_forever(poll_interval=0.5)
|
.. method:: BaseServer.serve_forever(poll_interval=0.5)
|
||||||
|
|
||||||
Handle requests until an explicit :meth:`shutdown` request. Polls for
|
Handle requests until an explicit :meth:`shutdown` request. Polls for
|
||||||
shutdown every *poll_interval* seconds.
|
shutdown every *poll_interval* seconds. It also calls
|
||||||
|
:meth:`service_actions` which may be used by a subclass or Mixin to provide
|
||||||
|
various cleanup actions. For e.g. ForkingMixin class uses
|
||||||
|
:meth:`service_actions` to cleanup the zombie child processes.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
Added service_actions call to the serve_forever method.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: BaseServer.service_actions()
|
||||||
|
|
||||||
|
This is called by the serve_forever loop. This method is can be overridden
|
||||||
|
by Mixin's to add cleanup or service specific actions.
|
||||||
|
|
||||||
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
.. method:: BaseServer.shutdown()
|
.. method:: BaseServer.shutdown()
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ On the other hand, if you are building e.g. an HTTP server, where all
|
||||||
data is stored externally (e.g. in the file system), a synchronous
|
data is stored externally (e.g. in the file system), a synchronous
|
||||||
class will essentially render the service "deaf" while one request is
|
class will essentially render the service "deaf" while one request is
|
||||||
being handled -- which may be for a very long time if a client is slow
|
being handled -- which may be for a very long time if a client is slow
|
||||||
to reqd all the data it has requested. Here a threading or forking
|
to recv all the data it has requested. Here a threading or forking
|
||||||
server is appropriate.
|
server is appropriate.
|
||||||
|
|
||||||
In some cases, it may be appropriate to process part of a request
|
In some cases, it may be appropriate to process part of a request
|
||||||
|
@ -170,6 +170,7 @@ class BaseServer:
|
||||||
- process_request(request, client_address)
|
- process_request(request, client_address)
|
||||||
- shutdown_request(request)
|
- shutdown_request(request)
|
||||||
- close_request(request)
|
- close_request(request)
|
||||||
|
- service_actions()
|
||||||
- handle_error()
|
- handle_error()
|
||||||
|
|
||||||
Methods for derived classes:
|
Methods for derived classes:
|
||||||
|
@ -225,6 +226,8 @@ class BaseServer:
|
||||||
r, w, e = select.select([self], [], [], poll_interval)
|
r, w, e = select.select([self], [], [], poll_interval)
|
||||||
if self in r:
|
if self in r:
|
||||||
self._handle_request_noblock()
|
self._handle_request_noblock()
|
||||||
|
|
||||||
|
self.service_actions()
|
||||||
finally:
|
finally:
|
||||||
self.__shutdown_request = False
|
self.__shutdown_request = False
|
||||||
self.__is_shut_down.set()
|
self.__is_shut_down.set()
|
||||||
|
@ -239,6 +242,14 @@ class BaseServer:
|
||||||
self.__shutdown_request = True
|
self.__shutdown_request = True
|
||||||
self.__is_shut_down.wait()
|
self.__is_shut_down.wait()
|
||||||
|
|
||||||
|
def service_actions(self):
|
||||||
|
"""Called by the serve_forever() loop.
|
||||||
|
|
||||||
|
May be overridden by a subclass / Mixin to implement any code that
|
||||||
|
needs to be run during the loop.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
# The distinction between handling, getting, processing and
|
# The distinction between handling, getting, processing and
|
||||||
# finishing a request is fairly arbitrary. Remember:
|
# finishing a request is fairly arbitrary. Remember:
|
||||||
#
|
#
|
||||||
|
@ -539,9 +550,15 @@ class ForkingMixIn:
|
||||||
"""
|
"""
|
||||||
self.collect_children()
|
self.collect_children()
|
||||||
|
|
||||||
|
def service_actions(self):
|
||||||
|
"""Collect the zombie child processes regularly in the ForkingMixin.
|
||||||
|
|
||||||
|
service_actions is called in the BaseServer's serve_forver loop.
|
||||||
|
"""
|
||||||
|
self.collect_children()
|
||||||
|
|
||||||
def process_request(self, request, client_address):
|
def process_request(self, request, client_address):
|
||||||
"""Fork a new subprocess to process the request."""
|
"""Fork a new subprocess to process the request."""
|
||||||
self.collect_children()
|
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid:
|
if pid:
|
||||||
# Parent process
|
# Parent process
|
||||||
|
@ -549,6 +566,7 @@ class ForkingMixIn:
|
||||||
self.active_children = []
|
self.active_children = []
|
||||||
self.active_children.append(pid)
|
self.active_children.append(pid)
|
||||||
self.close_request(request)
|
self.close_request(request)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
# Child process.
|
# Child process.
|
||||||
# This must never return, hence os._exit()!
|
# This must never return, hence os._exit()!
|
||||||
|
|
Loading…
Reference in New Issue