Skip to content

fix(stdio): restore 2s socket-release wait to prevent Windows port leak (#1173)#1175

Closed
Scriptwonder wants to merge 1 commit into
CoplayDev:betafrom
Scriptwonder:fix/issue-1173-stdio-tcp-leak
Closed

fix(stdio): restore 2s socket-release wait to prevent Windows port leak (#1173)#1175
Scriptwonder wants to merge 1 commit into
CoplayDev:betafrom
Scriptwonder:fix/issue-1173-stdio-tcp-leak

Conversation

@Scriptwonder
Copy link
Copy Markdown
Collaborator

@Scriptwonder Scriptwonder commented May 27, 2026

Summary

Root cause (from #1173)

PR #688 set the 2000ms wait specifically so the TcpListener socket could fully release on Windows before the post-reload Start() rebinds. PR #787 later reduced this to 500ms without flagging the Windows dependency.

On Windows, 500ms is too short for the OS to free the socket. After the 2nd play-mode entry within a single Editor session (default Enter Play Mode Options, Reload Domain enabled), the post-reload Start() hits AddressAlreadyInUse, the catch block silently falls back to a new port via PortManager.DiscoverNewPort(), and the Python server keeps talking to the orphan on the original port. Scene-graph tools (find_gameobjects, manage_scene get_hierarchy, execute_code) start returning busy/timeout indefinitely because OS round-robin sometimes routes calls to the orphaned listener.

Reporter (#1173) bisected to Reload Domain as the variable — Reload Domain OFF is clean across 4+ play cycles, consistent with the socket-release race.

Verified via git log -L that:

Scope

Two-line timeout restore; no behavioural change on macOS/Linux (where socket release is fast). The 1.5s extra wait only runs on Stop(), which is rare (domain reload / quit), so editor responsiveness is unaffected.

Test plan

Closes #1173

Summary by CodeRabbit

  • Bug Fixes
    • Extended editor shutdown timeout durations to allow proper OS-level socket cleanup, preventing port binding conflicts that could interrupt reload operations on Windows systems.

Review Change Stack

…ak (CoplayDev#1173)

PR CoplayDev#688 set toWait.Wait(2000) on StdioBridgeHost.Stop() specifically so the
TcpListener could fully release the port on Windows before the post-reload
Start() rebinds. PR CoplayDev#787 later reduced this to 500ms without flagging the
Windows dependency, and 9.7.1 ships the regressed value.

On Windows, 500ms is too short for the OS to free the socket. The post-reload
Start() then hits AddressAlreadyInUse, silently falls back to a new port via
PortManager.DiscoverNewPort(), and leaves the Python server pinned to the
original port. Scene-graph tools start returning busy/timeout indefinitely
because OS round-robin sometimes routes them to the orphaned listener.

- StdioBridgeHost.Stop: 500 -> 2000 (matches PR CoplayDev#688 intent)
- StdioBridgeReloadHandler.OnBeforeAssemblyReload: 500 -> 2000 (matches the
  StopAsync ceiling; was already too short for the same reason)

Reporter (CoplayDev#1173) bisected to Reload Domain ON; Reload Domain OFF is clean
across 4+ play cycles, consistent with the socket-release race.
Copilot AI review requested due to automatic review settings May 27, 2026 06:09
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6341f75a-3d83-4ae5-9fda-7b94cf6146a0

📥 Commits

Reviewing files that changed from the base of the PR and between 2d2bdc5 and cc9603e.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs
  • MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs

📝 Walkthrough

Walkthrough

Two files increase their shutdown wait timeouts from 500ms to 2000ms: StdioBridgeHost.Stop() waits longer for the listener task to exit, and StdioBridgeReloadHandler.OnBeforeAssemblyReload() waits longer after stopping the stdio transport. This prevents incomplete socket release on Windows before the next domain reload cycle.

Changes

Stdio Transport Shutdown Timeout Extension

Layer / File(s) Summary
Extend stdio shutdown wait for Windows socket release
MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs, MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs
StdioBridgeHost.Stop() waits 2000ms (instead of 500ms) for the listener task shutdown, and StdioBridgeReloadHandler.OnBeforeAssemblyReload() waits 2000ms after calling TransportManager.StopAsync(). Both timeouts are increased to allow Windows to fully release the socket before the next domain reload cycle rebinds.

Possibly related PRs

  • CoplayDev/unity-mcp#402: Introduced the ExclusiveAddressUse flag, dispose listener on shutdown, and assembly reload event handling; this PR restores the original 2000ms wait timeout that was part of that fix but was later reduced.

Poem

🐰 A socket, once bound, needs time to let go,
On Windows it's slower—the OS moves slow.
Five hundred ms? No! Give it two thousand instead,
Or orphaned listeners linger, all bloated and dead. 🕳️

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: restoring a 2s socket-release wait in StdioBridgeHost to prevent Windows port leaks after domain reloads.
Description check ✅ Passed The description is comprehensive and detailed, covering summary, root cause, scope, and test plan. However, some template checkboxes lack explicit marking despite content addressing those areas.
Linked Issues check ✅ Passed The PR fully addresses the core coding requirements from issue #1173: restoring 2000ms waits in StdioBridgeHost.Stop() and StdioBridgeReloadHandler.OnBeforeAssemblyReload to prevent Windows socket-release race conditions.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the issue: two-line timeout restorations in StdioBridgeHost.cs and StdioBridgeReloadHandler.cs. No extraneous modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Restores the original 2-second shutdown wait in the Unity Editor stdio transport teardown path to prevent Windows from rebinding the same TCP port before the OS fully releases the socket during domain reloads (fixing the port-leak/orphan-listener regression described in #1173).

Changes:

  • Restore Wait(2000) in StdioBridgeHost.Stop() to allow Windows time to fully release the disposed listener socket.
  • Restore the matching 2-second wait in StdioBridgeReloadHandler.OnBeforeAssemblyReload() to keep teardown timing consistent during domain reload.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs Increases the listener-task wait ceiling back to 2s to prevent Windows port reuse races after Stop()/Dispose.
MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs Aligns the pre-reload stop sequence timeout with the host stop timing during domain reloads.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +399 to 403
// Windows needs the full 2s for the socket to release after Dispose; with 500ms
// the next Start() can hit AddressAlreadyInUse and silently fall back to a new
// port, orphaning the listener. See #1173 / #688.
try { toWait.Wait(2000); } catch { }
}
Comment on lines 72 to +75
var stopTask = MCPServiceLocator.TransportManager.StopAsync(TransportMode.Stdio);
try { stopTask.Wait(500); } catch { }
// Match StdioBridgeHost.Stop's 2s socket-release wait; 500ms is too short on
// Windows and lets the next reload bind the same port before the OS frees it.
try { stopTask.Wait(2000); } catch { }
@Scriptwonder
Copy link
Copy Markdown
Collaborator Author

Closing — the 2000ms restore re-revives the backgrounded-Unity stall that PR #787 explicitly fixed by reducing this wait, and doesn't address the silent port fallback that's the real masking layer in #1173. A follow-up will rework the listener loop to exit promptly on cancellation (so Wait returns fast regardless of OS) and add a same-port retry before falling back to DiscoverNewPort, closing #1173 without trading off #787.

@Scriptwonder Scriptwonder deleted the fix/issue-1173-stdio-tcp-leak branch May 27, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants