Skip to content

Main window: stop timers and undo polling on close, start them on Loaded (UI suite 2m20s → ~55s) - #14593

Merged
niksedk merged 1 commit into
mainfrom
claude/threadpool-hunt
Sep 6, 2026
Merged

Main window: stop timers and undo polling on close, start them on Loaded (UI suite 2m20s → ~55s)#14593
niksedk merged 1 commit into
mainfrom
claude/threadpool-hunt

Conversation

@niksedk

@niksedk niksedk commented Sep 6, 2026

Copy link
Copy Markdown
Member

What

Follow-up to #14591: hunting the thread growth in the UI test suite with per-test dotnet-stack dumps.

Findings

Threads Where Cause
318 UiTickPump.Loop MainViewModel started its 50 ms position pump and 16 ms cursor pump in the constructor; a view model whose window closed without OnClosing (every test host, which detaches the save prompt) or that never had a window kept them forever. The 400 ms slow timer (a full GetFastHash pass) ran on the shared UI thread for every one of them too.
149 Dispatcher.Invoke in UndoRedoManager.CheckForChanges The 333 ms change-detection poll of every closed editor window kept firing; its blocking Invoke parked one pool thread per window for the rest of the process. This is the starvation behind the OverlappingTick flake and the 15 s mpv test in #14591.

The same leak exists in the app for File > New window: closing the second editor window left its pumps and undo poll running until exit.

Change

  • StartBackgroundWork() runs from OnLoaded instead of the constructor (the timers have nothing to drive before the window is up).
  • StopBackgroundWork() (timers + StopChangeDetection) runs from CleanUp and from the host window's Closed event, so it also covers a close that bypasses OnClosing.
  • The delayed StartChangeDetection in OnLoaded is skipped when the window is already gone.
  • Test speed-ups on top: MultipleReplaceViewModel.PreviewIntervalMs (internal) lets the tests wait 25 ms per preview settle instead of 250 ms; the fix-common-errors "Analysing…" delays are internal statics the unfixable-errors tests zero.

Numbers (local, full UITests)

Before After
Wall time 2 m 20 s 54 s – 61 s
Sum of test durations 125 s ~60 s
ThreadPool threads at end ~200 9
Multiple-replace test family 18.5 s 6.5 s
SubtitleGridScrollPerformanceTests 11.6 s 2.2 s

Two full runs clean (4895 passed, 1 skipped).

🤖 Generated with Claude Code

…rt them on Loaded

Hunting the thread growth seen in the UI test suite (ThreadPool at ~200
threads, process at ~900 by the end of a run) with per-test stack dumps:

- 318 threads sat in UiTickPump.Loop: MainViewModel started its 50 ms
  position pump and 16 ms cursor pump in the constructor and nothing
  stopped them for a view model whose window closed without OnClosing,
  or that never had a window. The 400 ms slow timer (a full GetFastHash
  pass) kept running on the shared UI thread for every one of them too.
- 149 thread-pool threads were parked in Dispatcher.Invoke inside
  UndoRedoManager.CheckForChanges: the 333 ms change-detection poll of
  every closed editor window kept firing, and its blocking Invoke never
  returned. That is the starvation behind the OverlappingTick flake and
  the 15 s mpv test.

In the app the same leak applies to File > New window: a second editor
window closed again left its pumps and its undo poll running for the
process lifetime.

StartBackgroundWork runs from OnLoaded (the timers have nothing to
drive before the window is up), StopBackgroundWork runs from CleanUp
and from the host window's Closed event, and the delayed
StartChangeDetection in OnLoaded is skipped when the window is already
gone.

Test-side speed-ups on top: the multiple-replace preview timer is
exposed as PreviewIntervalMs so the tests wait 25 ms instead of 250 ms
per settle, and the fix-common-errors "Analysing..." delays are
internal statics the unfixable-errors tests zero.

UI suite: 2 m 20 s -> ~55 s locally, thread pool stays at 9 threads.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@niksedk
niksedk merged commit d5086cf into main Sep 6, 2026
1 check passed
@niksedk
niksedk deleted the claude/threadpool-hunt branch September 6, 2026 05:53
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.

1 participant