26

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

My application handles only one request at time so I do not use separate threads for handling requests and I am unable to call shutdown() because it causes deadlock (it's not in the docs but it's stated directly in the source code of socketserver).

I will paste here a simplified version of my code for 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 is not working. I just wanted to show you the thing I would like to accomplish - to shutdown server and quit the application while waiting for incoming data when the user presses CtrlC.

Serge Stroobandt
  • 28,495
  • 9
  • 107
  • 102
samuelg0rd0n
  • 1,080
  • 2
  • 15
  • 27

9 Answers9

27

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()

Few notes:

  1. To kill server, do POST request to http://localhost:8000/kill_server.
  2. I create function which calls server.shutdown() and run it from another thread to solve the problem we discuss.
  3. I use advice from Binding Socket: “Address already in use” to make socket instantly avaliable for reuse (you can run server again without having [Errno 98] Address already in use error). With stock TCPServer you will have to wait for connection to timeout to run you server again.
Burak
  • 2,251
  • 1
  • 16
  • 33
dmitry_romanov
  • 5,146
  • 1
  • 33
  • 36
  • Why catch KeyboardInterrupt from serve_forever? – Jonathan Feb 02 '15 at 17:42
  • @Jonathan, it was done to free the socket properly using `server_close()`. As a result, you can restart the server without waiting for timeout untill pending connections do actually close. I retested the code, on Python 2.7.3 (Linux) it is already unnecessary. Thanks for pointing. – dmitry_romanov Feb 03 '15 at 11:33
18

The SocketServer library uses some weird ways of handling inherited attributes ( guessing due to use of old style classes). If you create a server and list it's protected attributes you 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 add the following in your request handler:

self.server._BaseServer__shutdown_request = True

The server will shutdown. This does the same thing as calling server.shutdown(), just without waiting (and deadlocking the main thread) until it's shutdown.

frmdstryr
  • 20,142
  • 3
  • 38
  • 32
  • 3
    Ugly but still the cleanest way I've found :/ The standard library should have a nowait argument so it can be called in the same thread – Romuald Brunet Jan 06 '17 at 13:37
5

You should call shutdown in other thread actually as pointed in 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()
ykhrustalev
  • 604
  • 10
  • 18
4

I believe the OP's intent is to shut down the server from the request handler and I think that the KeyboardInterrupt aspect of his code is just confusing things.

Pressing CtrlC from the shell where the server is running will succeed in shutting it down without doing anything special. You can't press CtrlC from a different shell and expect it to work, and I think that notion may be where this confusing code is coming from. There is no need to handle the KeyboardInterrupt in handle() as the OP tried, or around serve_forever() as another suggested. If you do neither, it works as expected.

The only trick here -- and it is tricky -- is telling the server to shutdown from the handler without deadlocking.

As the OP explained and showed in his code, he is using a single threaded server, so the user who suggested to shut it down in the "other thread" is not paying attention.

I dug around the SocketServer code and discovered that the BaseServer class, in its effort to work with the threaded mixins available in this module, actually makes it more difficult to use with non-threaded servers, by using a threading.Event around the loop in serve_forever.

So, I wrote a modified version of serve_forever for single-threaded servers which makes it possible to shut down the server from the request handler.

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 will end its serve_forever loop. You can use netcat to test this:

printf "shutdown" | nc localhost 52000
Serge Stroobandt
  • 28,495
  • 9
  • 107
  • 102
chadrik
  • 3,413
  • 1
  • 21
  • 19
  • I get a => r, w, e = SocketServer._eintr_retry(select.select, [self], [], [], AttributeError: 'module' object has no attribute '_eintr_retry' – KumZ Feb 04 '14 at 15:30
3

I used this in Python 3.7.4

def handle():
    setattr(self.server, '_BaseServer__shutdown_request', True)
Serge Stroobandt
  • 28,495
  • 9
  • 107
  • 102
Pent Down
  • 31
  • 1
2

If you do not catch the KeyboardInterrupt in the TCP handler (or if you re-raise it), it should trickle down to the root call, in this case the server_forever() call.

I have not tested this, though. The code would 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()
Serge Stroobandt
  • 28,495
  • 9
  • 107
  • 102
Ondergetekende
  • 1,065
  • 9
  • 22
  • 1
    Unfortunately, it doesn't work. I've already tried something like this. It writes an "exception happend" warning in the terminal and closes the connection (handle method returns) but serve_forver is still waiting for incoming connections and as as soon as one is accepted, application continues normally. To quit the application altogether, I need to press Ctrl-C so the "exception happened" warning appears and then Ctrl-C again before any connection is established. – samuelg0rd0n Apr 10 '12 at 09:42
  • I personally think it's not possible and I have only two options: either put handle method in different thread or not to use module socketserver and therefore create server manually (socket,bind,listen etc.) – samuelg0rd0n Apr 10 '12 at 10:28
  • seems like for a `http.server.HTTPServer` this simple approach works. If you Ctrl+C the server is properly shutdown from the same thread. – mihaipopescu Apr 09 '18 at 15:09
1

You could always try signals:

import signal
import os

# inside handler code that has decided to shutdown:
os.kill(os.getpid(), signal.SIGHUP)    # send myself sighup
Serge Stroobandt
  • 28,495
  • 9
  • 107
  • 102
upandacross
  • 417
  • 4
  • 7
0

Problem I found with many of the solutions posted is 'server not defined'. To get around that, save the server handle in the constructor. That way, a second server is not required to shut down the server.

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

class Handler(BaseHTTPRequestHandler):
    def __init__(self, request, client_address, server):
        # Save the server handle
        self.server = server
        BaseHTTPRequestHandler.__init__(self, request, client_address, server)

    # This can be do_POST or do_SET, whatever takes your fancy
    def do_GET(self):
        if 'exit' in self.path:
            # Commit suicide
            # Send OK
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
        
            # Tell sender what is happening
            self.wfile.write(bytes('Shutting down server', 'utf-8'))
            # Shut down server
            self.server.shutdown()

# Start server
port_server = ThreadingHTTPServer(('localhost', 5000), Handler)
port_server.serve_forever()
cup
  • 7,589
  • 4
  • 19
  • 42
0

I wasn't able to figure out how to do this with a non-threaded server. Starting the serve_forever loop in a thread was the only way I was able to get shutdown() to work, also from a different thread (specifically was hitting blocking/deadlocks on the self.__is_shut_down.wait() call mentioned in another answer)

class SimpleHttpServerAdapter:
    def init(self):
        self.__httpServer: HTTPServer | None = None

    def start(self):
        print("Starting simple HTTP server on port 8000")

        self.__httpServer = HTTPServer(("localhost", 8000), SimpleHTTPRequestHandler)

        def start_in_separate_thread(httpServer: HTTPServer | None):
            if httpServer:
                httpServer.serve_forever()

        thread = Thread(target=start_in_separate_thread, args=(self.__httpServer,))
        thread.start()

    def stop(self):
        print("Stopping simple HTTP server")

        def stop_in_separate_thread(httpServer: HTTPServer | None):
            if httpServer:
                httpServer.shutdown()

        thread = Thread(target=stop_in_separate_thread, args=(self.__httpServer,))
        thread.start()
        thread.join()
grunet
  • 311
  • 2
  • 9