fix: stop tearing down healthy HTTP sessions on transient probe misses (#1207)#1237
Conversation
CoplayDev#1207) The orphaned-session detector ended an active session on a SINGLE stale reachability reading, and the reading came from a lone 50ms TCP connect cached for 0.75s — trivially false-negative on a machine busy with test runs or domain reloads. Evidence bundles in CoplayDev#1207 show 13 teardowns and 147 socket closures in one session from exactly this loop, wedging the bridge in no_unity_session churn until manual recovery. - Require 3 consecutive failed polls (0.75s cadence) before declaring a session orphaned; probe readings taken while the editor is compiling or importing don't count toward teardown, and detection is skipped entirely while busy. - Raise the probe's connect wait 50ms -> 250ms, as an overall budget shared across candidate hosts so the worst-case main-thread wait cannot multiply. - Honor UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S above 20s (ceiling now 120s; default unchanged): the old ceiling equalled the default, silently neutering the documented escape hatch for projects whose reloads or test boundaries legitimately exceed 20s. Same treatment for UNITY_MCP_SESSION_READY_WAIT_SECONDS, and both now share one bounded env-read helper. The remaining piece of CoplayDev#1207 (keepalive reload-awareness in WebSocketTransportClient) is untouched here: the resume machinery reworked in CoplayDev#1234 already covers reload boundaries, and the detector debounce removes the dominant churn source dsarno identified.
📝 WalkthroughWalkthroughUnity local HTTP reachability probing now uses a longer shared timeout budget, and orphaned session teardown waits for repeated down probes. Server-side wait-time environment parsing is centralized with a higher clamp ceiling and new tests. ChangesUnity Editor: Reachability probing and orphan-session teardown
Server-side bounded wait-time environment handling
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant UpdateStartHttpButtonState
participant UpdateConnectionStatus
participant ShouldEndOrphanedSession
UpdateStartHttpButtonState->>UpdateStartHttpButtonState: probe local HTTP reachability
UpdateStartHttpButtonState->>UpdateStartHttpButtonState: update consecutiveServerDownPolls
UpdateConnectionStatus->>ShouldEndOrphanedSession: evaluate session and editor state
ShouldEndOrphanedSession-->>UpdateConnectionStatus: return teardown decision
UpdateConnectionStatus->>UpdateConnectionStatus: end orphaned session when allowed
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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 |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs (1)
342-366: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReset the orphaned-poll counter when the session starts
consecutiveServerDownPollsonly clears afterUpdateStartHttpButtonState()sees the server up again, so a stale count can survive a successfulStartAsync()and immediately satisfy orphan detection before the next fresh probe. Clear it when the session transitions to running.🤖 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 `@MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs` around lines 342 - 366, The orphaned-session detection in UpdateConnectionStatus can be triggered by a stale consecutiveServerDownPolls count after the session transitions to running. Reset consecutiveServerDownPolls when the connection successfully starts, and make sure the reset happens in the start-path tied to StartAsync/UpdateStartHttpButtonState so ShouldEndOrphanedSession only uses fresh probes.
🤖 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.
Outside diff comments:
In `@MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs`:
- Around line 342-366: The orphaned-session detection in UpdateConnectionStatus
can be triggered by a stale consecutiveServerDownPolls count after the session
transitions to running. Reset consecutiveServerDownPolls when the connection
successfully starts, and make sure the reset happens in the start-path tied to
StartAsync/UpdateStartHttpButtonState so ShouldEndOrphanedSession only uses
fresh probes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 39ada2ce-19ef-4b3b-955c-1851ab24acdd
📒 Files selected for processing (6)
MCPForUnity/Editor/Services/ServerManagementService.csMCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.csServer/src/transport/plugin_hub.pyServer/tests/test_plugin_hub_wait_env.pyTestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.csTestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs.meta
Review follow-up: a streak accumulated while no session was running (detector inert, counter still counting) survived into a freshly started session and could satisfy orphan detection before the first post-start probe refreshed. Reset on the not-running -> running transition, which covers every start path (manual Connect, auto-start, resume) since UpdateConnectionStatus runs on the UI tick.
There was a problem hiding this comment.
Pull request overview
This PR reduces unnecessary HTTP transport session churn by debouncing the Unity-side “orphaned session” teardown logic, making local reachability probing less hair-trigger, and by allowing the server-side session resolve/ready wait env overrides to extend beyond the previous hard 20s ceiling.
Changes:
- Debounce orphaned-session teardown to require 3 consecutive failed reachability polls, and skip/defer teardown while the editor is busy (compile/import) or a toggle is in progress.
- Increase local HTTP reachability probe connect budget (50ms → 250ms) and share that budget across candidate hosts to cap worst-case UI-thread blocking.
- Add a bounded env-read helper for PluginHub waits and extend max clamp to 120s, with new unit tests.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs.meta | Adds Unity asset metadata for the new EditMode regression test. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs | Adds EditMode regression tests for the orphaned-session teardown predicate. |
| Server/tests/test_plugin_hub_wait_env.py | Adds tests for bounded/clamped env overrides for session resolve waits. |
| Server/src/transport/plugin_hub.py | Introduces shared bounded env parsing for resolve/ready waits and raises the clamp ceiling. |
| MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs | Implements debounced orphan detection with busy-editor deferral and a testable predicate. |
| MCPForUnity/Editor/Services/ServerManagementService.cs | Increases local probe timeout and applies a shared overall timeout budget across probe hosts. |
Files not reviewed (1)
- TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs.meta: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| value = float(raw) | ||
| except ValueError as e: | ||
| logger.warning("Invalid %s=%r, using default %s: %s", name, raw, default_s, e) | ||
| value = default_s | ||
| return max(0.0, min(value, max_s)) |
| def test_invalid_falls_back_to_default(monkeypatch): | ||
| monkeypatch.setenv(ENV, "not-a-number") | ||
| assert _read_bounded_wait_env(ENV, default_s=20.0, max_s=120.0) == 20.0 |
Semantic coexistence resolution in McpConnectionSection.cs: keep both sibling debounce mechanisms intact — beta's health-check verification debounce (UnhealthyVerificationThreshold / ShouldReportUnhealthy / consecutiveVerifyFailures, c344625) and this branch's orphaned-session down-poll debounce (OrphanedSessionDownPollThreshold / ShouldEndOrphanedSession / consecutiveServerDownPolls + reset on session start). Both EditMode test files (McpConnectionSectionHealthDebounceTests, McpConnectionSectionOrphanDetectionTests) are preserved. Claude-Session: https://claude.ai/code/session_01C8TU8ibk8gv3h4LqBxPbQi
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Server/src/transport/plugin_hub.py (1)
928-935: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider narrowing the broad exception catch in
_available_instance_ids.Ruff flags line 934 as BLE001 (blind
except Exception). This is an error-path fallback returning[], so broad catching is defensible. If feasible, narrowing to(RegistryError, KeyError, RuntimeError)or at minimum adding alogger.debugwould improve diagnosability without changing behavior.🤖 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 `@Server/src/transport/plugin_hub.py` around lines 928 - 935, In `_available_instance_ids`, avoid the blind `except Exception` by catching the specific registry-related exceptions that `_registry.list_sessions` can raise, such as `RegistryError`, `KeyError`, and `RuntimeError`; if narrowing is not feasible, retain the fallback behavior but add a `logger.debug` call with exception context before returning `[]`.Source: Linters/SAST tools
🤖 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 `@Server/src/transport/plugin_hub.py`:
- Around line 928-935: In `_available_instance_ids`, avoid the blind `except
Exception` by catching the specific registry-related exceptions that
`_registry.list_sessions` can raise, such as `RegistryError`, `KeyError`, and
`RuntimeError`; if narrowing is not feasible, retain the fallback behavior but
add a `logger.debug` call with exception context before returning `[]`.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: aff63db1-8218-4cc6-9360-af3f81e5d8b4
📒 Files selected for processing (2)
MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.csServer/src/transport/plugin_hub.py
🚧 Files skipped from review as they are similar to previous changes (1)
- MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
Addresses the dominant churn source in #1207, following the fix order @dsarno proposed from the evidence bundle.
Problem
Two amplifiers turned ordinary reload/test boundaries into
no_unity_sessionwedges:McpConnectionSection.UpdateConnectionStatusends an active session the moment one cached reachability reading is false — and that reading is a lone TCP connect with a 50 ms wait, cached 0.75 s. On a machine busy with a test run or domain reload the probe times out against a perfectly healthy server; the detector then force-stops the bridge (13 teardowns / 147 socket closures in one session in the macOS / HTTP transport: bridge WebSocket repeatedly drops (close 1005) on reload/test boundaries → no_unity_session #1207 evidence bundle), feeding the reconnect churn that outruns the server's session-resolve window.UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_Sis documented as configurable but was clamped to a ceiling equal to its default (20 s), so users whose reloads legitimately exceed 20 s could not actually extend the window.Fix
internal staticpredicate with tests.UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_Sceiling raised to 120 s (default unchanged at 20 s);UNITY_MCP_SESSION_READY_WAIT_SECONDSlikewise (default 6 s). Both now share one bounded env-read helper with tests.What this deliberately does not do
WebSocketTransportClient— fix(editor): survive domain reloads in HTTP auto-start and reload-resume (#1229) #1234's resume rework already owns reload boundaries, and per the maintainer analysis the detector debounce removes the dominant churn source. If churn persists after both land, that's the next lever.Verification
cd Server && uv run pytest tests/ -q→ 1313 passed, 3 skipped (5 new env-clamp tests).tools/check-unity-versions.sh(2021.3 floor passes locally).Summary by CodeRabbit