Skip to content

fix(transport): guard liveness watchdog against long-running commands + tests#154

Merged
dsarno merged 1 commit into
fix/websocket-liveness-watchdogfrom
claude/unity-mcp-pr-151-review-kvsy47
Jun 15, 2026
Merged

fix(transport): guard liveness watchdog against long-running commands + tests#154
dsarno merged 1 commit into
fix/websocket-liveness-watchdogfrom
claude/unity-mcp-pr-151-review-kvsy47

Conversation

@dsarno

@dsarno dsarno commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Follow-up to #151 addressing the one correctness risk from review, plus unit tests.

Problem

ReceiveLoopAsync awaits HandleExecuteAsync inline, so while a command runs the loop stops calling ReceiveAsync and _lastInboundUtcTicks goes stale (server pings sent during execution sit unread in the buffer). A command that runs longer than _inboundLivenessTimeout (~37.5s) — play-mode test runs, large asset imports, batch/refresh ops with a server-assigned timeout above the liveness window — would make the watchdog Abort() the socket and cancel the in-flight command. Before #151 these survived (the separate keep-alive task kept sending pongs), so this was a regression introduced by the watchdog.

Fix

  • Track an in-flight command counter (_commandsInFlight), incremented around dispatch in HandleExecuteAsync.
  • The watchdog skips the abort while a command is in flight — that silence is expected (the receive loop is parked on dispatch, not on a dead socket).
  • Refresh liveness in a finally before decrementing, so the gap until the next ReceiveAsync drains the buffered server pings can't race into a false trip.
  • A genuinely dead socket still surfaces: the result-send fails or the next ReceiveAsync faults → existing HandleSocketClosureAsync path.

Refactor for testability

Extracted the two pure decisions into private static helpers so they can be unit-tested without sockets:

  • ComputeInboundLivenessTimeout(TimeSpan)max(30s, keepAlive × 2.5)
  • ShouldTripLivenessWatchdog(sinceInbound, timeout, commandsInFlight)

Tests

Added to WebSocketTransportClientTests.cs (same reflection pattern as the existing tests in that file):

  • Timeout derivation: 2.5× scaling, 30s floor (incl. zero cadence), large-cadence scaling.
  • Trip decision: trips on real silence; does not trip with a command in flight; no trip within timeout or exactly at it.

Could not compile-check locally (no Unity/.NET toolchain in this environment); logic and conventions verified by inspection against the existing test file.

https://claude.ai/code/session_01HKue2hxNbVaNNYkpYABqjp


Generated by Claude Code

The receive loop awaits HandleExecuteAsync inline, so while a command runs
it stops reading inbound frames and _lastInboundUtcTicks goes stale. A
command longer than the liveness timeout (~37.5s) — test runs, asset
imports, batch ops — would make the watchdog falsely abort the socket and
kill the in-flight command.

Track an in-flight command counter and skip the watchdog while a command is
executing; refresh liveness on completion (before decrementing) so the gap
until the next ReceiveAsync drains buffered server pings can't race into a
false trip. A genuinely dead socket still surfaces via the result-send
failure or the next faulted ReceiveAsync.

Extract the timeout derivation and trip decision into pure static helpers
and cover them with unit tests.
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9eb47920-4797-4baf-8c26-081a9f795bfa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/unity-mcp-pr-151-review-kvsy47

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dsarno dsarno merged commit 44f474e into fix/websocket-liveness-watchdog Jun 15, 2026
3 checks passed
dsarno added a commit that referenced this pull request Jun 15, 2026
…#154)

The receive loop awaits HandleExecuteAsync inline, so while a command runs
it stops reading inbound frames and _lastInboundUtcTicks goes stale. A
command longer than the liveness timeout (~37.5s) — test runs, asset
imports, batch ops — would make the watchdog falsely abort the socket and
kill the in-flight command.

Track an in-flight command counter and skip the watchdog while a command is
executing; refresh liveness on completion (before decrementing) so the gap
until the next ReceiveAsync drains buffered server pings can't race into a
false trip. A genuinely dead socket still surfaces via the result-send
failure or the next faulted ReceiveAsync.

Extract the timeout derivation and trip decision into pure static helpers
and cover them with unit tests.

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants