Repository navigation
chore: Simplify bridge heartbeat and disconnect handling - #1554
Conversation
Keep per-client write locks, request cancellation sources, and shutdown state owned by UnityCliLoopBridgeServer while moving the stateless heartbeat and disconnect-monitor loops behind injected infrastructure services.
📝 WalkthroughWalkthroughThis PR extracts heartbeat sending and client-disconnect monitoring logic from ChangesHeartbeat and Disconnect Monitor Extraction
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant UnityCliLoopBridgeServer
participant UnityCliLoopBridgeHeartbeatService
participant UnityCliLoopBridgeClientDisconnectMonitor
participant BridgeClientConnection
Client->>UnityCliLoopBridgeServer: request frame
UnityCliLoopBridgeServer->>UnityCliLoopBridgeHeartbeatService: SendHeartbeatsAsync()
UnityCliLoopBridgeServer->>UnityCliLoopBridgeClientDisconnectMonitor: MonitorClientDisconnectAsync(BridgeClientConnection)
loop poll interval
UnityCliLoopBridgeHeartbeatService->>BridgeClientConnection: write heartbeat frame
UnityCliLoopBridgeClientDisconnectMonitor->>BridgeClientConnection: check connected
end
BridgeClientConnection-->>UnityCliLoopBridgeClientDisconnectMonitor: disconnected
UnityCliLoopBridgeClientDisconnectMonitor->>UnityCliLoopBridgeServer: cancel CancellationTokenSource
UnityCliLoopBridgeServer->>UnityCliLoopBridgeHeartbeatService: StopHeartbeatsAsync()
UnityCliLoopBridgeServer->>UnityCliLoopBridgeClientDisconnectMonitor: StopClientDisconnectMonitorAsync()
UnityCliLoopBridgeServer-->>Client: final response
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Packages/src/Editor/Infrastructure/UnityCliLoopBridgeHeartbeatService.cs (1)
45-56: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNull-conditional cancel can hang the shutdown wait.
heartbeatCancellationSource?.Cancel()silently no-ops if the source is null, butawait heartbeatTaskafterward still waits for the loop to end — which, perSendHeartbeatsAsync, only happens on cancellation or a write failure. If callers ever pass a non-nullheartbeatTaskwith a nullheartbeatCancellationSource, this will hang indefinitely. The siblingUnityCliLoopBridgeClientDisconnectMonitor.StopClientDisconnectMonitorAsynccalls.Cancel()directly (no?.), which is the fail-fast convention used elsewhere in this codebase — this method should match that pattern (or assert the invariant) instead of silently swallowing a null source.🔧 Suggested fix
internal async Task StopHeartbeatsAsync( Task heartbeatTask, CancellationTokenSource heartbeatCancellationSource) { if (heartbeatTask == null) { return; } - heartbeatCancellationSource?.Cancel(); + Debug.Assert(heartbeatCancellationSource != null, "heartbeatCancellationSource must be provided when heartbeatTask is non-null."); + heartbeatCancellationSource.Cancel(); await heartbeatTask; }Based on learnings, the codebase follows a fail-fast/Debug.Assert convention for internal preconditions rather than silently no-op'ing invalid state, and the sibling disconnect-monitor class already follows this pattern without
?..🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Packages/src/Editor/Infrastructure/UnityCliLoopBridgeHeartbeatService.cs` around lines 45 - 56, The shutdown path in StopHeartbeatsAsync should not silently ignore a missing CancellationTokenSource while still awaiting the heartbeat loop. Update UnityCliLoopBridgeHeartbeatService.StopHeartbeatsAsync to follow the same fail-fast precondition style as UnityCliLoopBridgeClientDisconnectMonitor.StopClientDisconnectMonitorAsync: assert or require that heartbeatCancellationSource is non-null whenever heartbeatTask is non-null, then call Cancel() directly before awaiting the task. This keeps the heartbeat loop from hanging and aligns the method with the codebase’s internal invariant handling.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@Packages/src/Editor/Infrastructure/UnityCliLoopBridgeHeartbeatService.cs`:
- Around line 45-56: The shutdown path in StopHeartbeatsAsync should not
silently ignore a missing CancellationTokenSource while still awaiting the
heartbeat loop. Update UnityCliLoopBridgeHeartbeatService.StopHeartbeatsAsync to
follow the same fail-fast precondition style as
UnityCliLoopBridgeClientDisconnectMonitor.StopClientDisconnectMonitorAsync:
assert or require that heartbeatCancellationSource is non-null whenever
heartbeatTask is non-null, then call Cancel() directly before awaiting the task.
This keeps the heartbeat loop from hanging and aligns the method with the
codebase’s internal invariant handling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d06d4ba6-bdc6-4b40-b140-4c497760b2e0
⛔ Files ignored due to path filters (2)
Packages/src/Editor/Infrastructure/UnityCliLoopBridgeClientDisconnectMonitor.cs.metais excluded by none and included by nonePackages/src/Editor/Infrastructure/UnityCliLoopBridgeHeartbeatService.cs.metais excluded by none and included by none
📒 Files selected for processing (4)
Assets/Tests/Editor/JsonRpcHeartbeatTests.csPackages/src/Editor/Infrastructure/UnityCliLoopBridgeClientDisconnectMonitor.csPackages/src/Editor/Infrastructure/UnityCliLoopBridgeHeartbeatService.csPackages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs
Summary
User Impact
Changes
Verification
dist/darwin-arm64/uloop compile --project-path "$(git rev-parse --show-toplevel)"-> 0 errors / 0 warningsdist/darwin-arm64/uloop run-tests --project-path "$(git rev-parse --show-toplevel)" --test-mode EditMode --filter-type regex --filter-value "(JsonRpcHeartbeatTests|UnityCliLoopBridgeServerShutdownTests|UnityCliLoopServerControllerRecoveryTests|JsonRpcProcessorCliVersionGateTests|OnionAssemblyDependencyTests)"-> 122/122 passed