Skip to content

Commit 5e826e8

Browse files
committed
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.
1 parent dac9ace commit 5e826e8

File tree

2 files changed

+34
-3
lines changed

2 files changed

+34
-3
lines changed

Doc/library/socketserver.rst

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,21 @@ Server Objects
153153
.. method:: BaseServer.serve_forever(poll_interval=0.5)
154154

155155
Handle requests until an explicit :meth:`shutdown` request. Polls for
156-
shutdown every *poll_interval* seconds.
156+
shutdown every *poll_interval* seconds. It also calls
157+
:meth:`service_actions` which may be used by a subclass or Mixin to provide
158+
various cleanup actions. For e.g. ForkingMixin class uses
159+
:meth:`service_actions` to cleanup the zombie child processes.
157160

161+
.. versionchanged:: 3.3
162+
Added service_actions call to the serve_forever method.
163+
164+
165+
.. method:: BaseServer.service_actions()
166+
167+
This is called by the serve_forever loop. This method is can be overridden
168+
by Mixin's to add cleanup or service specific actions.
169+
170+
.. versionadded:: 3.3
158171

159172
.. method:: BaseServer.shutdown()
160173

Lib/socketserver.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
8282
data is stored externally (e.g. in the file system), a synchronous
8383
class will essentially render the service "deaf" while one request is
8484
being handled -- which may be for a very long time if a client is slow
85-
to reqd all the data it has requested. Here a threading or forking
85+
to recv all the data it has requested. Here a threading or forking
8686
server is appropriate.
8787
8888
In some cases, it may be appropriate to process part of a request
@@ -170,6 +170,7 @@ class BaseServer:
170170
- process_request(request, client_address)
171171
- shutdown_request(request)
172172
- close_request(request)
173+
- service_actions()
173174
- handle_error()
174175
175176
Methods for derived classes:
@@ -225,6 +226,8 @@ def serve_forever(self, poll_interval=0.5):
225226
r, w, e = select.select([self], [], [], poll_interval)
226227
if self in r:
227228
self._handle_request_noblock()
229+
230+
self.service_actions()
228231
finally:
229232
self.__shutdown_request = False
230233
self.__is_shut_down.set()
@@ -239,6 +242,14 @@ def shutdown(self):
239242
self.__shutdown_request = True
240243
self.__is_shut_down.wait()
241244

245+
def service_actions(self):
246+
"""Called by the serve_forever() loop.
247+
248+
May be overridden by a subclass / Mixin to implement any code that
249+
needs to be run during the loop.
250+
"""
251+
pass
252+
242253
# The distinction between handling, getting, processing and
243254
# finishing a request is fairly arbitrary. Remember:
244255
#
@@ -539,16 +550,23 @@ def handle_timeout(self):
539550
"""
540551
self.collect_children()
541552

553+
def service_actions(self):
554+
"""Collect the zombie child processes regularly in the ForkingMixin.
555+
556+
service_actions is called in the BaseServer's serve_forver loop.
557+
"""
558+
self.collect_children()
559+
542560
def process_request(self, request, client_address):
543561
"""Fork a new subprocess to process the request."""
544-
self.collect_children()
545562
pid = os.fork()
546563
if pid:
547564
# Parent process
548565
if self.active_children is None:
549566
self.active_children = []
550567
self.active_children.append(pid)
551568
self.close_request(request)
569+
return
552570
else:
553571
# Child process.
554572
# This must never return, hence os._exit()!

0 commit comments

Comments
 (0)