fix(http): don't let a half-open TLS connection wedge the accept loop #16

Merged
Pakobbix merged 1 commits from fix/iris-tls-handshake-wedge into master 2026-09-23 13:10:53 +00:00
Owner

Problem (ARIA journal 2026-09-11/23): the listening socket was SSL-wrapped, so serve_forever's accept() ran the TLS handshake inline. A client that completed TCP but vanished mid-handshake (phone losing network/VPN) blocked do_handshake() forever — no new device connections accepted (app couldn't reconnect); on restart stop()→httpd.shutdown() froze the event loop until the watchdog killed it.

Fix: (1) TLS handshake moved to the per-connection thread under a hard timeout (HANDSHAKE_TIMEOUT_S=10s); failed/timed-out handshakes just close the socket. (2) stop() runs shutdown()/server_close()/join in an executor under asyncio.wait_for(10s) — teardown can no longer freeze the event loop.

Test: new regression test (half-open silent TCP conn: fresh TLS /v1/health still 200, stop() bounded). Fails on old code, passes on fixed. 134/134 green, ruff clean.

Deploy: update /home/aria/.hermes/profiles/aria/plugins/iris-platform/ + restart.

**Problem** (ARIA journal 2026-09-11/23): the listening socket was SSL-wrapped, so `serve_forever`'s `accept()` ran the TLS handshake inline. A client that completed TCP but vanished mid-handshake (phone losing network/VPN) blocked `do_handshake()` forever — no new device connections accepted (app couldn't reconnect); on restart `stop()`→`httpd.shutdown()` froze the event loop until the watchdog killed it. **Fix**: (1) TLS handshake moved to the per-connection thread under a hard timeout (`HANDSHAKE_TIMEOUT_S=10s`); failed/timed-out handshakes just close the socket. (2) `stop()` runs `shutdown()`/`server_close()`/join in an executor under `asyncio.wait_for(10s)` — teardown can no longer freeze the event loop. **Test**: new regression test (half-open silent TCP conn: fresh TLS `/v1/health` still 200, `stop()` bounded). Fails on old code, passes on fixed. 134/134 green, ruff clean. **Deploy**: update `/home/aria/.hermes/profiles/aria/plugins/iris-platform/` + restart.
ARIA added 1 commit 2026-09-23 13:10:32 +00:00
fix(http): don't let a half-open TLS connection wedge the accept loop
CI / Kotlin tests (android host + desktop) (pull_request) Successful in 8m18s
CI / Gateway plugin tests (pull_request) Failing after 15m7s
2c20b1c8a5
A client that completes TCP but vanishes mid-TLS-handshake (e.g. a
phone losing its network/VPN while traveling) blocked
ssl.SSLSocket.accept() inside serve_forever forever: the gateway
stopped accepting any new device connections (the app could not
reconnect), and on the next restart httpd.shutdown() froze the whole
event loop until the shutdown watchdog killed the process (ARIA
journal 2026-09-11 / 2026-09-23).

- Move the TLS handshake out of the accept loop: it now runs in the
  per-connection thread under a hard timeout (HANDSHAKE_TIMEOUT_S,
  10 s); a failed/timed-out handshake just closes the socket.
- stop() no longer blocks the event loop: shutdown()/server_close()/
  join run in an executor under asyncio.wait_for(10 s); if the bound
  expires the daemon threads are abandoned.
- Regression test: a silent half-open TCP connection must not stop
  fresh TLS connections from being served, and stop() must stay
  bounded.
Collaborator

✅ No issues found — changes look consistent with the stated intent. Ready to be merged.

The implementation is clean and correct:

  • TLS moved to per-connection thread: process_request correctly spawns a thread that performs wrap_socket (handshake) under a hard timeout before delegating to the parent's process_request. The OSError catch covers both ssl.SSLError and socket.timeout/TimeoutError.
  • Bounded teardown: stop() runs shutdown()/server_close()/join in an executor with asyncio.wait_for(10s), and the except Exception correctly catches asyncio.TimeoutError (which is a subclass of Exception across all supported Python versions).
  • Test: The half-open TCP connection + fresh TLS health check + bounded stop() assertion directly validates the regression scenario. The monkeypatch of HANDSHAKE_TIMEOUT_S to 0.5s keeps the test fast.
✅ No issues found — changes look consistent with the stated intent. Ready to be merged. The implementation is clean and correct: - **TLS moved to per-connection thread**: `process_request` correctly spawns a thread that performs `wrap_socket` (handshake) under a hard timeout before delegating to the parent's `process_request`. The `OSError` catch covers both `ssl.SSLError` and `socket.timeout`/`TimeoutError`. - **Bounded teardown**: `stop()` runs `shutdown()`/`server_close()`/`join` in an executor with `asyncio.wait_for(10s)`, and the `except Exception` correctly catches `asyncio.TimeoutError` (which is a subclass of `Exception` across all supported Python versions). - **Test**: The half-open TCP connection + fresh TLS health check + bounded `stop()` assertion directly validates the regression scenario. The `monkeypatch` of `HANDSHAKE_TIMEOUT_S` to `0.5s` keeps the test fast.
Pakobbix merged commit 2c1d444348 into master 2026-09-23 13:10:53 +00:00
Pakobbix deleted branch fix/iris-tls-handshake-wedge 2026-09-23 13:10:53 +00:00
Sign in to join this conversation.