From 20cab38123a50065a68416c21b8058c750e0cec0 Mon Sep 17 00:00:00 2001 From: Bryce Boe Date: Mon, 15 Jun 2026 09:01:41 -0700 Subject: [PATCH] Exit cleanly on Ctrl-C Connection threads inherited ThreadingMixIn's default daemon_threads = False. Because RangeHandler forces HTTP/1.1, browsers hold keep-alive connections open, leaving worker threads blocked in recv(). On Ctrl-C the interpreter waited on those non-daemon threads forever, so the process printed "Goodbye" but never exited (issue #1). Mark connection threads as daemons so they cannot block shutdown, and close the listening socket in a finally block. Closes #1 Co-Authored-By: Claude Opus 4.8 (1M context) --- ext_http_server.py | 6 ++++++ tests/test_ext_http_server.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/ext_http_server.py b/ext_http_server.py index 58d1cdb..1545207 100644 --- a/ext_http_server.py +++ b/ext_http_server.py @@ -259,6 +259,10 @@ def __init__( class MyServer(socketserver.ThreadingMixIn, SecureHTTPServer): """A threaded SecureHTTPServer with basic error filtering.""" + # Run connection threads as daemons so a lingering HTTP/1.1 keep-alive + # connection cannot block interpreter shutdown on Ctrl-C (see issue #1). + daemon_threads = True + def handle_error(self, request: socket | tuple[bytes, socket], client_address: Any) -> None: # noqa: ANN401 """Disable tracebacks on connection close errors.""" _, exc_value, _ = sys.exc_info() @@ -317,6 +321,8 @@ def main() -> int: server.serve_forever() except KeyboardInterrupt: print("\nGoodbye") + finally: + server.server_close() return 0 diff --git a/tests/test_ext_http_server.py b/tests/test_ext_http_server.py index f5b0599..1cbee40 100644 --- a/tests/test_ext_http_server.py +++ b/tests/test_ext_http_server.py @@ -156,6 +156,12 @@ def test_server_serves_range_request(secure_server): assert body == b"56789ABCDEFGHIJ" +def test_server_uses_daemon_threads(): + # Daemon connection threads keep lingering keep-alive connections from + # blocking interpreter shutdown on Ctrl-C (see issue #1). + assert MyServer.daemon_threads is True + + def test_set_rate_limit_computes_block_size(): RateLimitWriter.set_rate_limit(128) assert RateLimitWriter.block_size == int(1024 * 128 * RateLimitWriter.INTERVAL_LEN)