Shutdown socketserver serve_forever () in a single threaded Python application

I know that socketserver has a shutdown () method that causes the server to shut down, but this only works in an application with multiple threads, since shutdown should be called from a different thread than the thread in which serve_forever () works.

My application processes only one request in time, so I do not use separate threads to process requests, and I can not call shutdown () because it causes a deadlock (this is not in the documents, but it is specified directly in the SocketServer source code).

Paste here a simplified version of my code for a better understanding:

import socketserver class TCPServerV4(socketserver.TCPServer): address_family = socket.AF_INET allow_reuse_address = True class TCPHandler(socketserver.BaseRequestHandler): def handle(self): try: data = self.request.recv(4096) except KeyboardInterrupt: server.shutdown() server = TCPServerV4((host, port), TCPHandler) server.server_forever() 

I am aware that this code does not work. I just wanted to show you what I would like to do - shut down the server and exit the application while waiting for incoming data when the user press Ctrl-C.

+16
python sockets socketserver
Apr 10 2018-12-12T00:
source share
6 answers

You can start another thread locally in your handler and call shutdown from there.

Working demo:

  #!/usr/bin/env python # -*- coding: UTF-8 -*- import SimpleHTTPServer import SocketServer import time import thread class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_POST(self): if self.path.startswith('/kill_server'): print "Server is going down, run it again manually!" def kill_me_please(server): server.shutdown() thread.start_new_thread(kill_me_please, (httpd,)) self.send_error(500) class MyTCPServer(SocketServer.TCPServer): def server_bind(self): import socket self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) server_address = ('', 8000) httpd = MyTCPServer(server_address, MyHandler) try: httpd.serve_forever() except KeyboardInterrupt: pass httpd.server_close() 

A few notes:

  • To kill the server, execute the POST request http://localhost:8000/kill_server .
  • I create a function that calls server.shutdown() and runs it from another thread to solve the problem that we are discussing.
  • I am using the advice of https://stackoverflow.com/a/3129298/ to make the socket instantly reusable (you can restart the server without having the [Errno 98] Address already using the error). With a margin of TCPServer, you will have to wait for a timeout connection to start the server again.
+14
Mar 20 '14 at 13:05
source share

The SocketServer library uses some strange ways to handle inherited attributes (guessing due to the use of old-style classes). If you create a server and list its protected attributes, you will see:

 In [4]: server = SocketServer.TCPServer(('127.0.0.1',8000),Handler) In [5]: server._ server._BaseServer__is_shut_down server.__init__ server._BaseServer__shutdown_request server.__module__ server.__doc__ server._handle_request_nonblock 

If you just added the following to the request handler:

 self.server._BaseServer__shutdown_request = True 

The server will shut down. This does the same thing as calling server.shutdown() , just not waiting (and blocking the main thread) until it finishes.

+6
Mar 15 '16 at 17:07
source share

I believe the OP's intention is to disconnect the server from the request handler, and I think that the KeyboardInterrupt aspect of its code just confuses things.

Pressing ctrl-c from the shell where the server is running will be able to close it without doing anything special. You cannot press ctrl-c from another shell and expect it to work, and I think that this may be the reason this confusing code arises. There is no need to handle KeyboardInterrupt in handle() when trying to OP, or near serve_forever() , as suggested by others. If you do not, it works as expected.

The only trick here - and this is difficult - is telling the server to exit the handler without blocking.

As the OP explains and shows in its code, it uses a single-threaded server, so the user who suggested closing it in a "different thread" does not pay attention.

I dug up the SocketServer code and found that the BaseServer class, which BaseServer to work with the streaming mixins available in this module, actually makes it difficult to use with non-streaming servers using threading.Event around the loop in serve_forever .

So, I wrote a modified version of serve_forever for single-threaded servers, which allows you to disconnect the server from the request processor.

 import SocketServer import socket import select class TCPServerV4(SocketServer.TCPServer): address_family = socket.AF_INET allow_reuse_address = True def __init__(self, server_address, RequestHandlerClass): SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass) self._shutdown_request = False def serve_forever(self, poll_interval=0.5): """provide an override that can be shutdown from a request handler. The threading code in the BaseSocketServer class prevented this from working even for a non-threaded blocking server. """ try: while not self._shutdown_request: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e = SocketServer._eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() finally: self._shutdown_request = False class TCPHandler(SocketServer.BaseRequestHandler): def handle(self): data = self.request.recv(4096) if data == "shutdown": self.server._shutdown_request = True host = 'localhost' port = 52000 server = TCPServerV4((host, port), TCPHandler) server.serve_forever() 

If you send the string 'shutdown' to the server, the server ends the serve_forever . You can use netcat to verify this:

 printf "shutdown" | nc localhost 52000 
+3
Jan 29 '14 at 20:57
source share

If you do not catch KeyboardInterrupt in the tcp handler (or if you raise it again), it should leak into the root call, in which case the server_forever() call.

I have not tested this. The code will look like this:

 import socketserver # Python2: SocketServer class TCPServerV4(socketserver.TCPServer): address_family = socket.AF_INET allow_reuse_address = True class TCPHandler(socketserver.BaseRequestHandler): def handle(self): data = self.request.recv(4096) server = TCPServerV4((host, port), TCPHandler) try: server.serve_forever() except KeyboardInterrupt: server.shutdown() 
+2
Apr 10 2018-12-12T00:
source share

You should call shutdown in another thread actually specified in the source code:

  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() 
+2
Aug 18 '13 at 8:01
source share

You can always try the signals:

Import signal import os

# code of the internal handler that decided to disable:

os.kill (os.getpid (), signal.SIGHUP) # send yourself sighup

+1
Sep 10 '14 at 17:14
source share



All Articles