From 2e9fbd1021d1887098a30c37490c7e5119faafcb Mon Sep 17 00:00:00 2001 From: Daliys Date: Tue, 14 Jul 2026 23:17:35 +0200 Subject: [PATCH] fix(mcp): resolve TriggerSafeAssetRefresh callback leaks and add timeout guard (#135) --- CHANGELOG.md | 1 + Editor/MCPServerMethods.Hierarchy.cs | 38 +++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d14858..1b1d126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable public changes to Nexus Unity are documented here. - Consolidated duplicate internal Ollama-review and serialized-property write helpers. ### Fixed +- `TriggerSafeAssetRefresh` callback is now tracked to prevent callback leaks, duplicate refresh hooks, and memory leaks across assembly reload/shutdown boundaries, with a fail-safe 15-second timeout guard. - `run_tests` now reports `Submitted` rather than `Success` after Unity accepts its asynchronous request, and `run_tests_wait` now returns rejected submissions immediately instead of polling until timeout. - Auto Setup now reports a clear refresh-and-retry message if a client is unavailable after the integration list is regenerated, instead of throwing an exception. diff --git a/Editor/MCPServerMethods.Hierarchy.cs b/Editor/MCPServerMethods.Hierarchy.cs index 5819930..31729ca 100644 --- a/Editor/MCPServerMethods.Hierarchy.cs +++ b/Editor/MCPServerMethods.Hierarchy.cs @@ -301,8 +301,27 @@ private static void EnsureParentDirectory(string fullPath) if (!string.IsNullOrEmpty(directory)) System.IO.Directory.CreateDirectory(directory); } + private static EditorApplication.CallbackFunction _pendingRefreshCallback; + + static MCPServerMethods() + { + AssemblyReloadEvents.beforeAssemblyReload += CleanupPendingRefresh; + EditorApplication.quitting += CleanupPendingRefresh; + } + + private static void CleanupPendingRefresh() + { + if (_pendingRefreshCallback != null) + { + EditorApplication.update -= _pendingRefreshCallback; + _pendingRefreshCallback = null; + } + } + private static void TriggerSafeAssetRefresh() { + CleanupPendingRefresh(); + _scriptRefreshBusyUntilUtc = DateTime.UtcNow.AddSeconds(8); // Scripts trigger domain reload which blocks the HTTP response. @@ -314,16 +333,27 @@ private static void TriggerSafeAssetRefresh() AppNapBypass.ScheduleActivation(); #endif - EditorApplication.CallbackFunction waitForFocus = null; - waitForFocus = () => { - EditorApplication.update -= waitForFocus; + double startTime = EditorApplication.timeSinceStartup; + double timeoutSeconds = 15.0; // Fail-safe fallback timeout + + _pendingRefreshCallback = () => { + var cb = _pendingRefreshCallback; + if (cb == null) return; + EditorApplication.update -= cb; + _pendingRefreshCallback = null; + + if (EditorApplication.timeSinceStartup - startTime > timeoutSeconds) + { + NexusEditorLog.Warning(NexusLogCategory.Api, "[MCP] TriggerSafeAssetRefresh timed out waiting for OS focus. Refresh aborted."); + return; + } // Trigger refresh immediately. With App Nap bypassed, this is reliable // even if the focus-switch (open -a) is still in flight. AssetDatabase.Refresh(); }; - EditorApplication.update += waitForFocus; + EditorApplication.update += _pendingRefreshCallback; } } }