fix(unity): concurrent, thread-safe, window-independent HTTP server - #8
Open
isekream wants to merge 1 commit into
Open
fix(unity): concurrent, thread-safe, window-independent HTTP server#8isekream wants to merge 1 commit into
isekream wants to merge 1 commit into
Conversation
…pendent
The editor MCP server had coupled defects causing requests to hang or
return empty bodies:
1. Serial request handling: HandleRequests processed each request inline
on the listener thread, blocking the accept loop for up to
requestTimeout seconds, so subsequent requests sat unanswered until
the current one finished. Each request is now serviced on the
ThreadPool so GetContext() loops immediately.
2. Cross-thread EditorApplication.delayCall: tool execution was dispatched
to the main thread via `delayCall +=` from the background listener
thread. delayCall is a non-thread-safe static delegate; concurrent
subscription drops callbacks, so the completion flag never flips and
the request times out with no body. Replaced with a thread-safe
ConcurrentQueue<Action> drained by a single persistent
EditorApplication.update pump.
3. Window-coupled registry + static-ctor crash: tools lived on the
EditorWindow instance, so a closed window made every call fail with
"window not open"; and LoadPreferences() called EditorPrefs.* directly
in the static constructor, which Unity forbids ("GetInt is not allowed
to be called from a ScriptableObject constructor"). The registry is
now static and initialized on server start; preference loading is
deferred to EditorApplication.delayCall.
Adds volatile completion state for correct cross-thread visibility.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2 tasks
Owner
Author
|
Superseded by #9, which reconciles this threading/headless rewrite with the static registry work and adds domain-reload survival, scene-building tools, Unity 6 compat, and bridge audit fixes. Safe to close. |
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.
Problem
The editor MCP server accepted connections but frequently returned empty bodies or timed out, and got worse under back-to-back calls. Three coupled defects:
Serial request handling —
HandleRequestsranProcessRequestinline on the single listener thread, which then blocks inExecuteTool's wait loop for up torequestTimeoutseconds. The nextGetContext()isn't reached until the current request finishes, so concurrent bridge calls stall in the accept backlog.Cross-thread
EditorApplication.delayCall +=— tool work was dispatched to the main thread by mutating thedelayCallstatic delegate from the background listener thread. Delegate+=is a non-atomic read-modify-write; concurrent subscription drops callbacks, so the completion flag never flips → timeout with no body. (Fragile even single-threaded — it's an editor-loop delegate being mutated off-thread.)Window-coupled registry + static-ctor crash — the tool registry lived on the
EditorWindowinstance, so a closed window made every call return "MCP Server window is not open." Separately,LoadPreferences()calledEditorPrefs.*directly in the static constructor, which Unity forbids (GetInt is not allowed to be called from a ScriptableObject constructor).Fix
delayCall +=dispatch with a thread-safeConcurrentQueue<Action>drained by one persistentEditorApplication.updatepump.staticand initialize it onStartServer()— the server now works headless (window closed).LoadPreferences()out of the static constructor intoEditorApplication.delayCall.volatilefields for correct visibility.Tool bodies still run serially on the main thread (Unity API requirement); only the listener and the per-request wait move off it.
Verification
Against a live editor:
scene.list_root_objectsreturned a full success body in ~76 ms (previously empty/timeout), and the "window not open" failure class is gone.Single file changed:
Unity/Editor/McpUnityServer.cs.🤖 Generated with Claude Code