0

I have a simple TCP server. There is an issue that occurs when I connect to the server using the Google Chrome browser: after a request, it seems to open another socket to send the next request there quickly next time. When such a socket is opened, I cannot restart my server gracefully. I can use the allow_reuse_address hack but it is not the thing I want to do because the port is still busy after the server shutdown, and if I wish another server to listen to this port, it is unable to do this.

I am sure there may be other cases when I don't want my port to be held by a nasty client and I would like to have the ability to force disconnect it. If I force to disconnect the server and then try to connect again, I get

OSError: [Errno 98] Address already in use

Here is an example. Let us imagine some "dirty client" connecting to my server and doing nothing (simply to consume the resources of my server). I want to shut down my server (let us say, for maintenance). I cannot do this until the client disconnects. I wish to disconnect it manually from the server-side.

from http.server import BaseHTTPRequestHandler
from socket import socket, AF_INET, SOCK_STREAM
from socketserver import TCPServer
from threading import Thread
from time import sleep, time


def dirty_client():
    client_socket = socket(AF_INET, SOCK_STREAM)
    client_socket.connect(("127.0.0.1", 8080))
    sleep(10)
    client_socket.close()


if __name__ == "__main__":
    # Spawn TCP server
    server = TCPServer(("127.0.0.1", 8080), BaseHTTPRequestHandler)
    thread = Thread(target=server.serve_forever)
    thread.start()

    # Block it with a connection (should be on client side)
    hang_thread = Thread(target=dirty_client)
    hang_thread.start()
    sleep(1)

    # Let it hang
    start = time()
    print("Shutting down...")
    server.shutdown()
    server.server_close()
    print(f"Success in {time() - start} seconds")
    thread.join()
    hang_thread.join()

The server hangs on the moment the request handler reads the socket. There is no timeout for reading and I cannot close the file during the read. The server itself can be shut down during the read, but the port will not be freed in this case.

It is possible to use BaseRequestHandler in this example — after it, the port is also in use. Though, BaseHTTPRequestHandler waits until the port is freed.

Charlie
  • 826
  • 1
  • 11
  • 27
  • “There is no timeout for reading". See [socket.settimeout](https://docs.python.org/3/library/socket.html#socket.socket.settimeout). Another option is the [select](https://docs.python.org/3/library/select.html) module. Don't read unless there is something to read. – Mark Tolonen Apr 26 '22 at 04:25
  • @MarkTolonen, thank you for noticing. The [socket.settimeout](https://docs.python.org/3/library/socket.html#socket.socket.settimeout) doesn't seem to cooperate well with [socket.makefile](https://docs.python.org/3/library/socket.html#socket.socket.makefile): it throws an exception but the client socket is still working and the port is still busy, so I cannot recreate the server for a while. The [select](https://docs.python.org/3/library/select.html) module allows not to get stuck on sockets without information sent though it doesn't allow to close them when I need (they are still hanging). – Charlie Apr 26 '22 at 07:13
  • Even when I close the file created with `makefile`, close the connection (from the server-side), and close the server socket, this doesn't affect the client socket and the port is still busy for a while. – Charlie Apr 26 '22 at 07:15
  • Your example doesn’t show makefile use. Make a [mcve]. – Mark Tolonen Apr 26 '22 at 12:20
  • @MarkTolonen, my question is still about the `TCPServer` abstraction. In the current CPython implementation, it uses `makefile`. I use the knowledge about the internals only to explain my concerns. Maybe I'm missing some functionality that is available out-of-the-box, ant that is why I am here. – Charlie Apr 26 '22 at 15:37

0 Answers0