Skip to content

Fix issues raised by the new CI gates - #37

Merged
bbangert merged 2 commits into
mainfrom
claude/serene-curie-xrjxef-ci-fixes
Oct 4, 2026
Merged

bbangert merged 2 commits into
mainfrom
claude/serene-curie-xrjxef-ci-fixes

Conversation

@bbangert

@bbangert bbangert commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Stacked on #36.

What changed

Of the new gates, only mix argus --fail-above 0 failed on existing code. Credo, dialyzer, hex audit, format and compile were already clean. Argus reported two findings, both in Espex.Connection. I treated both as real issues and fixed them rather than suppressing them.

1. Keepalive timer cancelled without flushing its message (argus.mailbox)

arm_keepalive/2 cancelled the old timer and armed a new Process.send_after(self(), :espex_keepalive, ms). If a tick was already in the mailbox when inbound data reset the clock, it was handled as if it came from the new timer. The existing comment called this "benign", but it can do more than send an early ping. After the grace re-arm (a ping is outstanding), a stale idle tick would be treated as the grace expiry and close a healthy connection early.

  • The timer is now armed with :erlang.start_timer(ms, self(), :espex_keepalive), which delivers {:timeout, ref, :espex_keepalive}.
  • handle_info/2 acts only when ref matches state.keepalive_timer. A tick with any other ref is dropped. Valid ticks behave exactly as before.
  • New test in test/espex/keepalive_test.exs: once a ping is outstanding, a stale-ref tick is ignored and the connection keeps cycling pings.

2. Unlinked spawn for connection-listener notifications (argus.failure)

notify_connections_changed/1 used a bare spawn. It now follows the OTP fire-and-forget convention:

  • Espex.Supervisor starts a per-instance Task.Supervisor named <server_name>.TaskSupervisor. The name comes from the internal Espex.Supervisor.task_supervisor_name/1 (@doc false). The Task.Supervisor starts after the two registries and before Server and ThousandIsland. Under :rest_for_one it therefore outlives every connection on shutdown, so the final disconnect notification can still be dispatched.
  • The name reaches connections through a new :task_supervisor handler option and a new ConnectionState.task_supervisor field.
  • Notifications go through Task.Supervisor.start_child/2. The callback keeps its existing catch-everything wrapper. A :exit from start_child (supervisor mid-restart) is caught and logged, so a notification can never crash the connection, as the previous code also guaranteed.
  • With no task supervisor (a ConnectionState built outside an Espex.Supervisor tree), the notification is skipped. Notifications were already best-effort, and connected_clients/1 remains the source of truth.

Public behaviour is unchanged: notifications are still detached, unordered and best-effort, as documented in Espex.ConnectionListener. The supervision tree has one more internal child, and the moduledoc's child list is updated (it now also lists the unique client registry, which it was missing).

Validation (local)

  • mix deps.get, mix compile --warnings-as-errors, mix format --check-formatted: pass
  • mix test: 4 doctests, 419 tests, 0 failures. The keepalive, connected_clients and disconnect_clients suites also passed 3 repeated runs.
  • mix credo --strict: no issues
  • mix dialyzer: passed
  • mix argus --fail-above 0: 0 findings
  • mix hex.audit, mix deps.unlock --check-unused: clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01UmkxCtMk3Y8vf2b42ZyVTu


Generated by Claude Code

Argus reported two findings in Espex.Connection:

- The keepalive tick was a bare :espex_keepalive message, so a tick
  already delivered when its timer was cancelled was handled as the
  current one. Arm it with :erlang.start_timer/3 and act only on the ref
  held in :keepalive_timer; drop stale ticks.
- connection_listener notifications used a bare spawn. Run them under a
  per-instance Task.Supervisor started by Espex.Supervisor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmkxCtMk3Y8vf2b42ZyVTu
@bbangert
bbangert requested a balanced review from Copilot October 4, 2026 18:43
Base automatically changed from claude/serene-curie-xrjxef-ci to main October 4, 2026 18:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The task supervisor is unbounded, and the timer regression test does not distinguish the fixed implementation from the previous one.

Review effort: Balanced
Findings: 1 High severity · 1 Low severity

Open (2)
What changed in this PR

Fixes two Argus findings in connection lifecycle handling.

Changes:

  • Identifies keepalive timers by reference and ignores stale ticks.
  • Supervises connection-listener notifications with a per-instance Task.Supervisor.
  • Adds regression coverage and supporting state/documentation updates.
File Description
lib/​espex/​connection.ex Handles referenced timers and supervised notifications.
lib/​espex/​connection_state.ex Stores the task supervisor name.
lib/​espex/​supervisor.ex Adds the task supervisor to the supervision tree.
test/​espex/​keepalive_test.exs Adds stale-timer coverage.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/espex/supervisor.ex Outdated
Comment thread test/espex/keepalive_test.exs Outdated
Cap the per-instance listener Task.Supervisor at 16 children so a slow
listener under connection churn sheds best-effort notifications instead
of accumulating tasks. Replace the stale-tick test with two that fail
against the pre-fix handler: a tick for the armed timer must ping, and
the tick of a timer cancelled by a real reset must be ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmkxCtMk3Y8vf2b42ZyVTu

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The timer race and unbounded notification process concerns are correctly addressed with focused regression coverage.

Review effort: Balanced
Findings: None

Resolved since last review (2)

@bbangert
bbangert merged commit 19dd9be into main Oct 4, 2026
6 checks passed
@bbangert
bbangert deleted the claude/serene-curie-xrjxef-ci-fixes branch October 4, 2026 18:53
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.

3 participants