fix: prevent FD exhaustion from WebSocket reconnect loop#112
Merged
CoderGamester merged 1 commit intoCoderGamester:mainfrom Feb 21, 2026
Merged
Conversation
…er#110) The Node.js MCP client used ws.close() for graceful WebSocket shutdown, but close() leaves the socket alive during the TCP close handshake. When reconnection fires immediately after, the old and new sockets overlap, accumulating file descriptors on the Unity side. Since websocket-sharp uses Mono's IOSelector (select()), FD values exceeding ~1024 crash Unity with "System.NotSupportedException: Could not register to wait for file descriptor N". Three-layer fix: 1. **Node.js — Always terminate():** Replace ws.close() with ws.terminate() in closeWebSocket(). This sends TCP RST and releases the FD immediately, preventing overlap with the next connection. Null the ws reference and strip all handlers before terminating to prevent stale event callbacks. 2. **Node.js — Cap reconnect attempts:** Change maxReconnectAttempts from -1 (unlimited) to 50. Even with terminate(), a permanently unavailable server would retry forever; 50 attempts with exponential backoff covers ~14 hours of downtime. 3. **Unity C# — Close stale sessions on connect:** In McpUnitySocketHandler.OnOpen(), enumerate existing sessions and close any that aren't the new connection. This cleans up zombie sessions that survived a Node.js process restart. Tested locally: 20+ rapid MCP tool calls over 3 minutes, FD count stayed at exactly 280 (zero growth). Unity logs confirmed stale connection cleanup: "Closed 1 stale connection(s) to accept new client".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #110 — Mono IOSelector FD exhaustion crash caused by WebSocket reconnect loop.
The Node.js MCP client used
ws.close()for graceful shutdown, butclose()leaves the socket alive during the TCP close handshake. When reconnection fires immediately, old and new sockets overlap, accumulating file descriptors on the Unity side. Since websocket-sharp uses Mono'sIOSelector(select()), FD values exceeding ~1024 crash Unity withSystem.NotSupportedException: Could not register to wait for file descriptor N.Changes (3 layers of defense)
terminate(): Replacews.close()withws.terminate()incloseWebSocket(). Sends TCP RST, releases FD immediately, prevents overlap. Nullswsreference and strips handlers before terminating.maxReconnectAttemptsfrom-1(unlimited) →50. With exponential backoff this covers ~14 hours of downtime before giving up.McpUnitySocketHandler.OnOpen()now enumerates existing sessions and closes any that aren't the new connection, cleaning up zombies from Node.js restarts.Test Evidence
Deployed fix locally (Unity 6.3 LTS, macOS, Ultraleap project) and ran 20+ rapid MCP tool calls over 3 minutes while monitoring Unity's FD count:
FD monitor log (sampled every 25s):
Unity console confirmed stale connection cleanup working:
All 96 existing tests pass (
npm test).Related