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.
diff --git a/Lib/SocketServer.py b/Lib/SocketServer.py
index 5506aa5..2eed914 100644
--- a/Lib/SocketServer.py
+++ b/Lib/SocketServer.py
@@ -158,6 +158,7 @@
     - 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 variables that may be overridden by derived classes or
     instances:
 
+    - timeout
     - address_family
     - socket_type
     - allow_reuse_address
@@ -182,6 +184,8 @@
 
     """
 
+    timeout = None
+
     def __init__(self, server_address, RequestHandlerClass):
         """Constructor.  May be extended, do not override."""
         self.server_address = server_address
@@ -204,8 +208,9 @@
     # 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 @@
     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 @@
                 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 @@
     - 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 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 @@
 
     """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 @@
             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()