Fix #1050: defer re-close off the Closing event (VerifyNotClosing throw)#1109
Merged
Conversation
…ing throw MainWindow_Closing cancels the first close, runs async cleanup, then calls Close() again. That second call is only safe if one of the preceding awaits actually suspended (yielding to WPF, which clears Window._isClosing). When the cleanup runs straight through without suspending — MCP disabled, so StopMcpServerAsync completes synchronously, and the collector already idle, so BackgroundService.StopAsync's Task.WhenAny completes synchronously too — the Close() at the end fires while we're still inside the first Closing event with _isClosing == true, and InternalClose → VerifyNotClosing() throws "Cannot ... call Close ... while a Window is closing." That timing dependence is why it was intermittent (a relaunch with the collector mid-cycle gave StopAsync something to await, so the re-close landed a beat later and worked). Defer the re-close with Dispatcher.BeginInvoke so this Closing event fully unwinds — clearing _isClosing — before the real close runs. The existing two-pass contract is unchanged: the queued Close() re-enters the handler, hits _closingCleanupDone, returns early, and the window closes for real. Latent bug in the close path, surfaced (not caused) by the #1050 relaunch fix once a normally-rendered window could be cleanly closed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What
MainWindow_Closingcancels the first close, runs async cleanup, then callsClose()again at the end. That re-close is only safe if one of the precedingawaits actually suspended (yielding to WPF, which clearsWindow._isClosing). When the cleanup runs straight through without suspending — MCP disabled (StopMcpServerAsynccompletes synchronously) and the collector already idle (BackgroundService.StopAsync'sTask.WhenAnycompletes synchronously) — the finalClose()fires while we're still inside the firstClosingevent with_isClosing == true, andInternalClose → VerifyNotClosing()throws:That timing dependence is exactly why it was intermittent in the field report (#1050): a relaunch with the collector mid-cycle gave
StopAsyncsomething real to await, so the re-close landed a beat later and worked.Fix
Defer the re-close with
Dispatcher.BeginInvoke(new Action(Close))so thisClosingevent fully unwinds — clearing_isClosing— before the real close runs. The existing two-pass contract is unchanged: the queuedClose()re-enters the handler, hits_closingCleanupDone, returns early, and the window closes for real.This was a latent bug in the close path, surfaced (not caused) by the #1050 relaunch fix — once a normally-rendered window could be cleanly closed, the re-entrancy became reachable.
Test plan
WPF window-lifecycle change, not unit-testable; validate manually:
Reported by @bbishop-neovest in #1050 (his #5/#6 observations pinned the timing).
🤖 Generated with Claude Code