Files
Hy-Memory/tests/test_server_shutdown.py
T
638d1b7bde fix(server): avoid deadlock when shutting down on signals (#89)
BaseServer.shutdown() waits for serve_forever() to return. The SIGINT /
SIGTERM handlers called it directly, and signal handlers run on the same
thread that is executing serve_forever(), so the server could never exit:
the process hung until SIGKILL and the client was never closed.

Ask a helper thread to call shutdown() and move server_close()/client.close()
into a finally block after serve_forever() returns. Add an in-process test
and a subprocess test that fires SIGALRM while serving on the main thread.

Co-authored-by: qiye.sjh <shijiahao_0802@youzan.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-10-21 12:19:33 +08:00

77 lines
2.2 KiB
Python

# -*- coding: utf-8 -*-
"""HTTP server signal shutdown must not deadlock the serving thread."""
import subprocess
import sys
import textwrap
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from hy_memory.server import _request_shutdown
class _Handler(BaseHTTPRequestHandler):
def do_GET(self): # pragma: no cover - the test only needs a live server
self.send_response(200)
self.end_headers()
def log_message(self, *args): # pragma: no cover - silence test output
pass
def test_request_shutdown_returns_promptly():
server = ThreadingHTTPServer(("127.0.0.1", 0), _Handler)
server.daemon_threads = True
thread = threading.Thread(target=server.serve_forever)
thread.start()
try:
started = time.monotonic()
_request_shutdown(server)
assert time.monotonic() - started < 1.0
thread.join(timeout=2.0)
assert not thread.is_alive()
finally:
server.server_close()
def test_signal_handler_does_not_deadlock_the_serving_thread():
"""A signal handler calling shutdown() directly hangs forever.
Run the serving loop on the main thread (as run_server does), fire an
alarm, and require the process to exit on its own.
"""
script = textwrap.dedent(
"""
import signal
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from hy_memory.server import _request_shutdown
class H(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
def log_message(self, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), H)
def _handler(signum, frame):
_request_shutdown(server)
signal.signal(signal.SIGALRM, _handler)
signal.alarm(1)
server.serve_forever()
server.server_close()
print("exited")
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
timeout=10,
)
assert result.returncode == 0, result.stderr
assert "exited" in result.stdout