Skip to content

fix(websocket): log on_disconnect errors, gate on accept, correct protocol docs + tests (v26.06.19)#45

Merged
ancongui merged 1 commit into
mainfrom
fix/websocket-lifecycle
Jun 6, 2026
Merged

fix(websocket): log on_disconnect errors, gate on accept, correct protocol docs + tests (v26.06.19)#45
ancongui merged 1 commit into
mainfrom
fix/websocket-lifecycle

Conversation

@ancongui
Copy link
Copy Markdown
Contributor

@ancongui ancongui commented Jun 6, 2026

Summary

An audit while validating the implement-websocket skill (which validated clean — messages flow, broadcast, and disconnect cleanup all proven over a real WS test client + live server) found real quality issues in the previously-untested pyfly.websocket module (322 lines, no tests).

Fixed

  1. on_disconnect cleanup errors were silently swallowed. The adapter wrapped the hook in contextlib.suppress(Exception) with no logging, so a failing cleanup (leaked resource/lock) vanished. Now logged (warning + traceback), matching the handler-error path.
  2. on_disconnect fired even when the connection was never accepted (unconditional finally). WebSocketSession now tracks accepted, and the adapter gates the hook on it — no spurious disconnect for a handshake that never completed.
  3. WebSocketHandler docstrings were misleading. They implied on_connect/on_message are auto-dispatched; they are not (only on_disconnect is — the @websocket_mapping method owns accept + the receive loop). Implementing on_message expecting framework dispatch was a silent no-op. Docstrings now state they're caller-invoked. (The skill already documented this correctly.)

Added

  • WebSocketSession.accepted property.
  • tests/websocket/ suite (5 tests) — message flow, disconnect cleanup, accept-gating + error-logging, the accepted flag, and the on_message-not-auto-dispatched contract. The gate + logging tests fail without the adapter fix (verified).

Notes

  • Documented that the WS controller instance is a singleton shared across all connections — per-connection state belongs on the WebSocketSession, not self.

Gates

mypy --strict (607) ✓ · ruff + ruff format ✓ · full suite 3725 passed, 1 skipped.

Bumps v26.06.18 → v26.06.19, CHANGELOG, uv.lock.

…tocol docs + tests + bump v26.06.19

Surfaced by an audit while validating implement-websocket (skill clean: messages flow,
broadcast, disconnect cleanup all proven). The websocket module was untested (322 lines).

- adapter: on_disconnect cleanup errors were swallowed by contextlib.suppress(Exception)
  with no logging -> now logged (warning + traceback), matching the handler-error path.
- adapter: on_disconnect ran unconditionally in finally even when the handler raised
  before accept() -> now gated on the new WebSocketSession.accepted flag.
- handler: WebSocketHandler docstrings implied on_connect/on_message are auto-dispatched
  (they are not; only on_disconnect is) -> corrected to state they are caller-invoked.
  Implementing on_message expecting framework dispatch was a silent no-op.
- decorators: documented that the controller instance is a singleton shared across all
  connections (per-connection state belongs on the WebSocketSession, not self).

Added WebSocketSession.accepted + tests/websocket/ (5 tests; module was untested).
Regression guards confirmed to fail without the adapter fix. Gates: mypy --strict (607),
ruff + format, full suite 3725 passed.
@ancongui ancongui merged commit c2c47f3 into main Jun 6, 2026
5 checks passed
@ancongui ancongui deleted the fix/websocket-lifecycle branch June 6, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant