Author zmwangx
Recipients paul.moore, steve.dower, tim.golden, zach.ware, zmwangx
Date 2020-07-29.18:13:11
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1596046392.19.0.70440136745.issue41437@roundup.psfhosted.org>
In-reply-to
Content
I noticed that on Windows, socket operations like recv appear to always block SIGINT until it's done, so if a recv hangs, Ctrl+C cannot interrupt the program. (I'm a *nix developer investigating a behavioral problem of my program on Windows, so please excuse my limited knowledge of Windows.)

Consider the following example where I spawn a TCP server that stalls connections by 5 seconds in a separate thread, and use a client to connect to it on the main thread. I then try to interrupt the client with Ctrl+C.

    import socket
    import socketserver
    import time
    import threading


    interrupted = threading.Event()


    class HoneypotServer(socketserver.TCPServer):
        # Stall each connection for 5 seconds.
        def get_request(self):
            start = time.time()
            while time.time() - start < 5 and not interrupted.is_set():
                time.sleep(0.1)
            return self.socket.accept()


    class EchoHandler(socketserver.BaseRequestHandler):
        def handle(self):
            data = self.request.recv(1024)
            self.request.sendall(data)


    class HoneypotServerThread(threading.Thread):
        def __init__(self):
            super().__init__()
            self.server = HoneypotServer(("127.0.0.1", 0), EchoHandler)

        def run(self):
            self.server.serve_forever(poll_interval=0.1)


    def main():
        start = time.time()
        server_thread = HoneypotServerThread()
        server_thread.start()
        sock = socket.create_connection(server_thread.server.server_address)
        try:
            sock.sendall(b"hello")
            sock.recv(1024)
        except KeyboardInterrupt:
            print(f"processed SIGINT {time.time() - start:.3f}s into the program")
            interrupted.set()
        finally:
            sock.close()
            server_thread.server.shutdown()
            server_thread.join()


    if __name__ == "__main__":
        main()

On *nix systems the KeyboardInterrupt is processed immediately. On Windows, the KeyboardInterrupt is always processed more than 5 seconds into the program, when the recv is finished.

I suppose this is a fundamental limitation of Windows? Is there any workaround (other than going asyncio)?

Btw, I learned about SIGBREAK, which when unhandled seems to kill the process immediately, but that means no chance of cleanup. I tried to handle SIGBREAK but whenever a signal handler is installed, the behavior reverts to that of SIGINT -- the handler is called only after 5 seconds have passed.

(I'm attaching a socket_sigint_sigbreak.py which is a slightly expanded version of my sample program above, showing my attempt at handler SIGBREAK. Both

    python .\socket_sigint_sigbreak.py --sigbreak-handler interrupt

and

    python .\socket_sigint_sigbreak.py --sigbreak-handler exit

stall for 5 seconds.)
History
Date User Action Args
2020-07-29 18:13:12zmwangxsetrecipients: + zmwangx, paul.moore, tim.golden, zach.ware, steve.dower
2020-07-29 18:13:12zmwangxsetmessageid: <1596046392.19.0.70440136745.issue41437@roundup.psfhosted.org>
2020-07-29 18:13:12zmwangxlinkissue41437 messages
2020-07-29 18:13:12zmwangxcreate