proxy_protocol_server.py hangs on SIGTERM with Python 3.13

XMLWordPrintableJSON

    • Type: Sub-task
    • Resolution: Fixed
    • Priority: Major - P3
    • 8.3.0-rc0
    • Affects Version/s: None
    • Component/s: None
    • None
    • DevProd Correctness
    • Fully Compatible
    • Correctness 2026-01-26, Correctness 2026-02-09
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Error:

      Test proxy_protocol_connect.js times out waiting for proxy server to terminate.
      Signal handler fires but server never exits.
      

      Description:

      Python 3.13's asyncio made breaking changes to server shutdown behavior that exposed issues in the proxy-protocol library.

      Root Causes:

      1. Python 3.13 added Server.close_clients(): In Python 3.13+, calling server.close() alone only stops accepting new connections but leaves existing connections open indefinitely. The new Server.close_clients() method (gh-113538) is required to forcibly disconnect active connections during shutdown.

      2. Python 3.12+ fixed wait_closed() bug: Python 3.10/3.11 had a bug where wait_closed() returned immediately even with active connections. Python 3.12+ fixed this to correctly wait. The proxy-protocol library's original code relied on this bug - it only called close() and assumed wait_closed() would return quickly. With the bug fixed, wait_closed() now hangs indefinitely waiting for connections that were never closed.

      Why It Worked in Python 3.10/3.11:
      The wait_closed() bug masked the missing close_clients() call. Even though active connections weren't closed, wait_closed() returned immediately, allowing the server to exit.

      Why It Fails in Python 3.12+:
      With the wait_closed() bug fixed, the server correctly waits for connections to close. Since close_clients() was never called, connections remain open and wait_closed() hangs forever.

      Fix:
      Monkey-patched the proxy-protocol library's run() function to:
      1. Call server.close_clients() before forever.cancel() in the signal handler
      2. Use loop.add_signal_handler() instead of signal.signal() to ensure signal handling runs on the event loop thread
      3. Added production logging for shutdown signal and completion

      References:

            Assignee:
            Nicholas Jefferies
            Reporter:
            Nicholas Jefferies
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: