]> granicus.if.org Git - python/commitdiff
Patch #742598 from Michael Pomraning: add .timeout attribute to SocketServer that...
authorAndrew M. Kuchling <amk@amk.ca>
Sat, 19 Jan 2008 16:26:13 +0000 (16:26 +0000)
committerAndrew M. Kuchling <amk@amk.ca>
Sat, 19 Jan 2008 16:26:13 +0000 (16:26 +0000)
.handle_timeout() method when no requests are received within the timeout period.

Doc/library/socketserver.rst
Lib/SocketServer.py
Misc/ACKS
Misc/NEWS

index c900ea71581920d61888dce034621f6193ac3787..2c85c863a25715bf09e39d4d3719536df08c5af0 100644 (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.
 
 Server classes have the same external methods and attributes, no matter what
-network protocol they use:
+network protocol they use.
 
 
 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
    :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
 server classes like :class:`TCPServer`; these methods aren't useful to external
 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
    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)
 
index 5506aa5b8ea8815e04f159d8fa824e16b784effe..2eed9144921f192456ac9df47c1bed18c7504020 100644 (file)
@@ -158,6 +158,7 @@ class BaseServer:
     - server_bind()
     - server_activate()
     - get_request() -> request, client_address
+    - handle_timeout()
     - verify_request(request, client_address)
     - server_close()
     - process_request(request, client_address)
@@ -171,6 +172,7 @@ class BaseServer:
     Class variables that may be overridden by derived classes or
     instances:
 
+    - timeout
     - address_family
     - socket_type
     - allow_reuse_address
@@ -182,6 +184,8 @@ class BaseServer:
 
     """
 
+    timeout = None
+
     def __init__(self, server_address, RequestHandlerClass):
         """Constructor.  May be extended, do not override."""
         self.server_address = server_address
@@ -204,8 +208,9 @@ class BaseServer:
     # finishing a request is fairly arbitrary.  Remember:
     #
     # - handle_request() is the top-level call.  It calls
-    #   get_request(), verify_request() and process_request()
-    # - get_request() is different for stream or datagram sockets
+    #   await_request(), verify_request() and process_request()
+    # - get_request(), called by await_request(), is different for
+    #   stream or datagram sockets
     # - process_request() is the place that may fork a new process
     #   or create a new thread to finish the request
     # - finish_request() instantiates the request handler class;
@@ -214,7 +219,7 @@ class BaseServer:
     def handle_request(self):
         """Handle one request, possibly blocking."""
         try:
-            request, client_address = self.get_request()
+            request, client_address = self.await_request()
         except socket.error:
             return
         if self.verify_request(request, client_address):
@@ -224,6 +229,28 @@ class BaseServer:
                 self.handle_error(request, client_address)
                 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):
         """Verify the request.  May be overridden.
 
@@ -289,6 +316,7 @@ class TCPServer(BaseServer):
     - server_bind()
     - server_activate()
     - get_request() -> request, client_address
+    - handle_timeout()
     - verify_request(request, client_address)
     - process_request(request, client_address)
     - close_request(request)
@@ -301,6 +329,7 @@ class TCPServer(BaseServer):
     Class variables that may be overridden by derived classes or
     instances:
 
+    - timeout
     - address_family
     - socket_type
     - request_queue_size (only for stream sockets)
@@ -405,11 +434,12 @@ class ForkingMixIn:
 
     """Mix-in class to handle each request in a new process."""
 
+    timeout = 300
     active_children = None
     max_children = 40
 
     def collect_children(self):
-        """Internal routine to wait for died children."""
+        """Internal routine to wait for children that have exited."""
         while self.active_children:
             if len(self.active_children) < self.max_children:
                 options = os.WNOHANG
@@ -424,6 +454,13 @@ class ForkingMixIn:
             if not pid: break
             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):
         """Fork a new subprocess to process the request."""
         self.collect_children()
index 2ae4528b0ac0a5b266a2d1efd17a78b96ffaac11..cf65424e4ac3146c021ae5e38cf09ee06243ea40 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -521,6 +521,7 @@ Martijn Pieters
 François Pinard
 Zach Pincus
 Michael Piotrowski
+Michael Pomraning
 Iustin Pop
 John Popplewell
 Amrit Prem
index 35d96344be8d2d694040cddffd8aa0332cb017b7..9e25b9669625c82ceb99079fbfbcaae40dcb25c4 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -704,6 +704,9 @@ Library
   be equal to calling getsockname() on the server's socket. Fixed by
   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,
   Python would crash instead of raising an error.