Skip to content

fix(server): stop reporting domain-reload cancellation as a request error - #49

Merged
Winlifes merged 1 commit into
FunplayAI:mainfrom
dehuaichendragonplus:fix/dont-log-reload-cancellation-as-error
Aug 10, 2026
Merged

fix(server): stop reporting domain-reload cancellation as a request error#49
Winlifes merged 1 commit into
FunplayAI:mainfrom
dehuaichendragonplus:fix/dont-log-reload-cancellation-as-error

Conversation

@dehuaichendragonplus

Copy link
Copy Markdown
Contributor

Problem

Any MCP request that is still in flight when Unity unloads the scripting domain is reported as a server error:

[Funplay MCP Server] Error handling request: A task was canceled.

This shows up during ordinary work — a script recompile, request_recompile, execute_code's pre-refresh, a Play Mode transition — and reads like a plugin failure even though nothing failed.

Root cause

The message wording pins the source: A task was canceled. is TaskCanceledException's message (an OperationCanceledException thrown by hand would read "The operation was canceled."), i.e. await on a TaskCompletionSource that was cancelled.

beforeAssemblyReload
  -> RootScopeServices.OnBeforeAssemblyReload   _serviceProvider.Dispose()
  -> EditorThreadHelper.Dispose()               cancels every queued TaskCompletionSource
                                                (and returns an already-cancelled task for
                                                 work submitted after disposal)
  -> the await in MCPServerService.HandleRequestReceived throws TaskCanceledException
  -> the catch-all logs Debug.LogError(...) and answers -32603 Internal error

EditorThreadHelperLifecycleTests already covers the cancellation source (ExecuteAsyncOnEditorThreadAsync_CancelsQueuedOuterTaskWhenDisposed, ..._RejectsNewWorkAfterDispose); what was missing is the request handler recognising that cancellation for what it is.

Two consequences: console noise (and an interrupted Error Pause / a false positive in CI log scans), plus the client receiving -32603 Internal error for a transient, retryable condition — while the broker returns -32001 … retryable: true for exactly the same situation.

Change

HandleRequestReceived gains a cancellation filter ahead of the catch-all: log through PluginDebugLogger instead of Debug.LogError, and answer with the broker's backend-unavailable payload (-32001, retryable: true, reason: unity_backend_reloading) so a client sees the same shape whether the broker or the in-process server answers.

This cannot mask a genuine failure: nothing in this path is cancellable per request — HandleRequestAsync is called with a default token, and no tool under Editor/Tools raises OperationCanceledException/TaskCanceledException — so a cancellation here can only mean the editor-thread pump went away, i.e. stop or domain reload.

The neighbouring "handler is null" path was intentionally left at -32000 MCP server is stopping or not ready. to keep this change to one behaviour.

Tests

Three new EditMode tests:

  • DisposedPumpRaisesTheCancellationTheServerTreatsAsShutdown — ties the two halves together: disposing a real EditorThreadHelper with queued work makes the awaiting caller observe TaskCanceledException, and IsShutdownCancellation classifies it.
  • ShutdownCancellation_CoversTaskCancellationButNotRealFailuresTaskCanceledException/OperationCanceledException classify; InvalidOperationException and null do not.
  • BackendUnavailableResponse_MatchesBrokerRetryablePayload — code, message and retryable/reason data match the broker's BackendUnavailable* constants.

Full Funplay.Editor.Tests EditMode run is green on Unity 6000.3.13f1 apart from two failures that are pre-existing in this (very large) host project and unrelated to the change: CapabilityFunctionsTests.FindReferences_FindsPrefabAndHonorsScanLimit (the reverse-dependency scan budget of 5000 assets / 10 s is exhausted before it reaches the temp prefab) and a flaky HttpMCPTransportLifecycleTests.StartAsync_UnresponsivePortOwnerFailsWithoutReportingRunning (an unrelated Spine asset import error in the host project is caught as an unhandled log; passes on re-run).

A request in flight when Unity unloads the scripting domain logged
"Error handling request: A task was canceled." and answered -32603
Internal error. Disposing the editor-thread pump (the DI container is
disposed on beforeAssemblyReload, and MCPServerService disposes it on
stop) cancels queued work, so the awaiting request observes a
TaskCanceledException that the catch-all treated as a failure.

Recognise cancellation as a stop/reload condition: log it through
PluginDebugLogger and answer with the broker's backend-unavailable
payload (-32001, retryable: true, reason: unity_backend_reloading) so
clients see one shape for this condition. Nothing here is cancellable
per request (HandleRequestAsync takes a default token and no tool
raises OperationCanceledException), so no real failure is reclassified.

Adds three EditMode tests, including one that ties a disposed pump's
cancellation to the new classification.
@Winlifes
Winlifes merged commit 964fa78 into FunplayAI:main Aug 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants