Patch #742598 from Michael Pomraning: add .timeout attribute to SocketServer that will call

.handle_timeout() method when no requests are received within the timeout period.
This commit is contained in:
Andrew M. Kuchling 2008-01-19 16:26:13 +00:00
parent 5e3745c886
commit e45a77adbe
4 changed files with 60 additions and 5 deletions

View File

@ -44,7 +44,7 @@ to behave autonomously; the default is :const:`False`, meaning that Python will
not exit until all threads created by :class:`ThreadingMixIn` have exited. not exit until all threads created by :class:`ThreadingMixIn` have exited.
Server classes have the same external methods and attributes, no matter what Server classes have the same external methods and attributes, no matter what
network protocol they use: network protocol they use.
Server Creation Notes Server Creation Notes
@ -193,6 +193,13 @@ The server classes support the following class variables:
The type of socket used by the server; :const:`socket.SOCK_STREAM` and The type of socket used by the server; :const:`socket.SOCK_STREAM` and
:const:`socket.SOCK_DGRAM` are two possible values. :const:`socket.SOCK_DGRAM` are two possible values.
.. data:: timeout
Timeout duration, measured in seconds, or :const:`None` if no timeout is desired.
If no incoming requests are received within the timeout period,
the :meth:`handle_timeout` method is called and then the server resumes waiting for
requests.
There are various server methods that can be overridden by subclasses of base There are various server methods that can be overridden by subclasses of base
server classes like :class:`TCPServer`; these methods aren't useful to external server classes like :class:`TCPServer`; these methods aren't useful to external
users of the server object. users of the server object.
@ -220,6 +227,13 @@ users of the server object.
method raises an exception. The default action is to print the traceback to method raises an exception. The default action is to print the traceback to
standard output and continue handling further requests. standard output and continue handling further requests.
.. function:: handle_timeout()
This function is called when the :attr:`timeout` attribute has been set to a
value other than :const:`None` and the timeout period has passed with no
requests being received. The default action for forking servers is
to collect the status of any child processes that have exited, while
in threading servers this method does nothing.
.. function:: process_request(request, client_address) .. function:: process_request(request, client_address)

View File

@ -158,6 +158,7 @@ class BaseServer:
- server_bind() - server_bind()
- server_activate() - server_activate()
- get_request() -> request, client_address - get_request() -> request, client_address
- handle_timeout()
- verify_request(request, client_address) - verify_request(request, client_address)
- server_close() - server_close()
- process_request(request, client_address) - process_request(request, client_address)
@ -171,6 +172,7 @@ class BaseServer:
Class variables that may be overridden by derived classes or Class variables that may be overridden by derived classes or
instances: instances:
- timeout
- address_family - address_family
- socket_type - socket_type
- allow_reuse_address - allow_reuse_address
@ -182,6 +184,8 @@ class BaseServer:
""" """
timeout = None
def __init__(self, server_address, RequestHandlerClass): def __init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override.""" """Constructor. May be extended, do not override."""
self.server_address = server_address self.server_address = server_address
@ -204,8 +208,9 @@ class BaseServer:
# finishing a request is fairly arbitrary. Remember: # finishing a request is fairly arbitrary. Remember:
# #
# - handle_request() is the top-level call. It calls # - handle_request() is the top-level call. It calls
# get_request(), verify_request() and process_request() # await_request(), verify_request() and process_request()
# - get_request() is different for stream or datagram sockets # - get_request(), called by await_request(), is different for
# stream or datagram sockets
# - process_request() is the place that may fork a new process # - process_request() is the place that may fork a new process
# or create a new thread to finish the request # or create a new thread to finish the request
# - finish_request() instantiates the request handler class; # - finish_request() instantiates the request handler class;
@ -214,7 +219,7 @@ class BaseServer:
def handle_request(self): def handle_request(self):
"""Handle one request, possibly blocking.""" """Handle one request, possibly blocking."""
try: try:
request, client_address = self.get_request() request, client_address = self.await_request()
except socket.error: except socket.error:
return return
if self.verify_request(request, client_address): if self.verify_request(request, client_address):
@ -224,6 +229,28 @@ class BaseServer:
self.handle_error(request, client_address) self.handle_error(request, client_address)
self.close_request(request) self.close_request(request)
def await_request(self):
"""Call get_request or handle_timeout, observing self.timeout.
Returns value from get_request() or raises socket.timeout exception if
timeout was exceeded.
"""
if self.timeout is not None:
# If timeout == 0, you're responsible for your own fd magic.
import select
fd_sets = select.select([self], [], [], self.timeout)
if not fd_sets[0]:
self.handle_timeout()
raise socket.timeout("Listening timed out")
return self.get_request()
def handle_timeout(self):
"""Called if no new request arrives within self.timeout.
Overridden by ForkingMixIn.
"""
pass
def verify_request(self, request, client_address): def verify_request(self, request, client_address):
"""Verify the request. May be overridden. """Verify the request. May be overridden.
@ -289,6 +316,7 @@ class TCPServer(BaseServer):
- server_bind() - server_bind()
- server_activate() - server_activate()
- get_request() -> request, client_address - get_request() -> request, client_address
- handle_timeout()
- verify_request(request, client_address) - verify_request(request, client_address)
- process_request(request, client_address) - process_request(request, client_address)
- close_request(request) - close_request(request)
@ -301,6 +329,7 @@ class TCPServer(BaseServer):
Class variables that may be overridden by derived classes or Class variables that may be overridden by derived classes or
instances: instances:
- timeout
- address_family - address_family
- socket_type - socket_type
- request_queue_size (only for stream sockets) - request_queue_size (only for stream sockets)
@ -405,11 +434,12 @@ class ForkingMixIn:
"""Mix-in class to handle each request in a new process.""" """Mix-in class to handle each request in a new process."""
timeout = 300
active_children = None active_children = None
max_children = 40 max_children = 40
def collect_children(self): def collect_children(self):
"""Internal routine to wait for died children.""" """Internal routine to wait for children that have exited."""
while self.active_children: while self.active_children:
if len(self.active_children) < self.max_children: if len(self.active_children) < self.max_children:
options = os.WNOHANG options = os.WNOHANG
@ -424,6 +454,13 @@ class ForkingMixIn:
if not pid: break if not pid: break
self.active_children.remove(pid) self.active_children.remove(pid)
def handle_timeout(self):
"""Wait for zombies after self.timeout seconds of inactivity.
May be extended, do not override.
"""
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() self.collect_children()

View File

@ -521,6 +521,7 @@ Martijn Pieters
François Pinard François Pinard
Zach Pincus Zach Pincus
Michael Piotrowski Michael Piotrowski
Michael Pomraning
Iustin Pop Iustin Pop
John Popplewell John Popplewell
Amrit Prem Amrit Prem

View File

@ -704,6 +704,9 @@ Library
be equal to calling getsockname() on the server's socket. Fixed by be equal to calling getsockname() on the server's socket. Fixed by
patch #1545011. patch #1545011.
- Patch #742598: Add .timeout attribute to SocketServer that calls
.handle_timeout() when no requests are received.
- Bug #1651235: When a tuple was passed to a ctypes function call, - Bug #1651235: When a tuple was passed to a ctypes function call,
Python would crash instead of raising an error. Python would crash instead of raising an error.