Thursday, September 22, 2011

How to Implement a Shutdown Method in SocketServer.TCPServer for Python 2.5

Prior to Python 2.6, the SocketServer.TCPServer doesn't have a shutdown method. The SocketServer.TCPServer.serve_forever() also blocks. Here is a TCPServer implementation that works in Python 2.5, implements a shutdown method, and support non-blocking request. The implementation here is based on the TCPServer implementation in Python 2.6.
class Py25TCPServer(SocketServer.ThreadingTCPServer):
   def __init__(self, address_tuple, handler):
      SocketServer.ThreadingTCPServer.__init__(self, address_tuple, handler)
      self.__is_shut_down = threading.Event()
      self.__shutdown_request = False

   def serve_forever(self, poll_interval=0.5):
      """Handle one request at a time until shutdown.

         Polls for shutdown every poll_interval seconds. Ignores
         self.timeout. If you need to do periodic tasks, do them in
         another thread.
      """
      self.__is_shut_down.clear()
      try:
         while not self.__shutdown_request:
            r, w, e = select.select([self], [], [], poll_interval)
            if self in r:
               self._handle_request_noblock()
      finally:
         self.__shutdown_request = False
         self.__is_shut_down.set()

   def shutdown(self):
      """Stops the serve_forever loop.

         Blocks until the loop has finished. This must be called while
         serve_forever() is running in another thread, or it will
         deadlock.
      """
      self.__shutdown_request = True
      self.__is_shut_down.wait()

   def _handle_request_noblock(self):
      """Handle one request, without blocking.

         I assume that select.select has returned that the socket is
         readable before this function was called, so there should be
         no risk of blocking in get_request().
      """
      try:
         request, client_address = self.get_request()
      except socket.error:
         return
      if self.verify_request(request, client_address):
         try:
            self.process_request(request, client_address)
         except:
            self.handle_error(request, client_address)
            self.close_request(request)

To make our program work in both Python 2.5 and 2.6+, we can do something like this.
import sys

python_version = sys.version_info
server = None
if python_version[1] < 6:
    server = Py25TCPServer((self.host, self.port), MyHandler)
else:
    server = SocketServer.TCPServer((self.host, self.port), MyHandler)
server.server_forever()