Skip to content

fix: stop tearing down healthy HTTP sessions on transient probe misses (#1207)#1237

Draft
Scriptwonder wants to merge 2 commits into
CoplayDev:betafrom
Scriptwonder:fix/issue-1207-orphan-detector-debounce
Draft

fix: stop tearing down healthy HTTP sessions on transient probe misses (#1207)#1237
Scriptwonder wants to merge 2 commits into
CoplayDev:betafrom
Scriptwonder:fix/issue-1207-orphan-detector-debounce

Conversation

@Scriptwonder

@Scriptwonder Scriptwonder commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

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_session wedges:

  1. Hair-trigger orphan detector. McpConnectionSection.UpdateConnectionStatus ends 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.
  2. Neutered escape hatch. UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S is 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

  • Orphan teardown now requires 3 consecutive failed polls at the 0.75 s cadence (~2.25 s of continuous unreachability); readings taken while the editor is compiling/importing don't count, and detection is skipped while busy. The decision is an internal static predicate with tests.
  • Probe connect wait raised 50 ms → 250 ms, implemented as an overall budget shared across candidate hosts so the worst-case main-thread wait cannot multiply (a refused connect still fails in ~1 ms; the budget only bites when packets are dropped or the stack is starved).
  • UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S ceiling raised to 120 s (default unchanged at 20 s); UNITY_MCP_SESSION_READY_WAIT_SECONDS likewise (default 6 s). Both now share one bounded env-read helper with tests.

What this deliberately does not do

Verification

  • cd Server && uv run pytest tests/ -q1313 passed, 3 skipped (5 new env-clamp tests).
  • 7 new EditMode tests covering the teardown predicate matrix (threshold, below-threshold, busy-editor deferral, toggle-in-progress, non-HTTP-local, not-running).
  • Compile-only matrix via tools/check-unity-versions.sh (2021.3 floor passes locally).

Summary by CodeRabbit

  • Bug Fixes
    • Improved local server detection so brief connection hiccups are less likely to interrupt an active session.
    • Prevented premature session shutdown while the editor is busy or when connection state is still fluctuating.
    • Increased the wait time for local connection checks, making startup and reconnect behavior more tolerant.
    • Expanded supported wait-time settings, with safer handling for invalid or out-of-range values.

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.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c2198c30-c865-43ba-a174-f44c0e61a3e7

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
📝 Walkthrough

Walkthrough

Adjusts Unity editor local HTTP server reachability probing to use a longer overall timeout with a shared time budget across hosts, and introduces a consecutive-down-poll threshold to defer orphaned-session teardown. Server-side, centralizes environment-variable wait-time parsing/clamping in plugin_hub.py with an expanded ceiling, plus new tests.

Changes

Unity Editor: Reachability probing and orphan-session teardown

Layer / File(s) Summary
Local port reachability timeout budget
MCPForUnity/Editor/Services/ServerManagementService.cs
Increases the overall probe timeout from 50ms to 250ms and changes TryConnectToLocalPort to treat the timeout as a shared budget tracked via stopwatch across all candidate hosts, rather than a full timeout per host.
Orphan-session teardown threshold policy
MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs, TestProjects/.../McpConnectionSectionOrphanDetectionTests.cs*
Adds a consecutiveServerDownPolls counter, an OrphanedSessionDownPollThreshold constant, and ShouldEndOrphanedSession helper requiring multiple consecutive down probes (and editor not busy) before ending an HTTP Local session; wires this into UpdateConnectionStatus and UpdateStartHttpButtonState, with new NUnit tests covering threshold, busy-editor, toggle-in-progress, and transport-type edge cases.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Server-side bounded wait-time environment handling

Layer / File(s) Summary
Bounded env wait-time helper and call sites
Server/src/transport/plugin_hub.py, Server/tests/test_plugin_hub_wait_env.py
Adds _read_bounded_wait_env to parse, warn on invalid values, and clamp environment-driven wait times; applies it in _resolve_session_id and the readiness fast-path in send_command_for_instance, raising the clamp ceiling from 20s to 120s; adds pytest tests validating default, override, ceiling clamp, and negative-value clamp behaviors.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Poller as UpdateStartHttpButtonState
  participant Status as UpdateConnectionStatus
  participant Policy as ShouldEndOrphanedSession

  Poller->>Poller: probe local HTTP server reachability
  Poller->>Poller: increment/reset consecutiveServerDownPolls
  Status->>Policy: check editorBusy, toggleInProgress, downPollCount
  Policy-->>Status: return true/false
  Status->>Status: end session if true
Loading

Possibly related issues

Possibly related PRs

  • CoplayDev/unity-mcp#510: Both modify _resolve_session_id and the readiness fast-path in plugin_hub.py to use bounded/clamped environment-variable wait times.
  • CoplayDev/unity-mcp#517: Both change McpConnectionSection's orphaned HTTP local session teardown logic.
  • CoplayDev/unity-mcp#556: Extends the same IsLocalHttpServerReachable/TryConnectToLocalPort probe logic this PR further refines.

Suggested reviewers: dsarno

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: preventing teardown of healthy HTTP sessions on transient probe misses.
Description check ✅ Passed The description is detailed and covers the problem, fix, verification, and related issue, though it omits some template sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@Scriptwonder Scriptwonder added the bug Something isn't working label Jul 4, 2026
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 91.66667% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Server/src/transport/plugin_hub.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Scriptwonder Scriptwonder added the safe-to-test Triggers CI checks label Jul 4, 2026
@Scriptwonder

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Reset the orphaned-poll counter when the session starts
consecutiveServerDownPolls only clears after UpdateStartHttpButtonState() sees the server up again, so a stale count can survive a successful StartAsync() 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

📥 Commits

Reviewing files that changed from the base of the PR and between d87ca19 and 18158fa.

📒 Files selected for processing (6)
  • MCPForUnity/Editor/Services/ServerManagementService.cs
  • MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
  • Server/src/transport/plugin_hub.py
  • Server/tests/test_plugin_hub_wait_env.py
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs
  • TestProjects/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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working safe-to-test Triggers CI checks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants