mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-09-02 04:01:25 +02:00
## Summary Fixes a ThreadPool worker leak in PowerToys Run that can eventually surface as `System.OutOfMemoryException` from `Thread.StartInternal` after rapid typing and repeated stale-query cancellation. Related: #36041 and duplicate reports #45704, #36587, #39942, #20264, and #8878. ## Root cause `MainViewModel.QueryResults` stored the active cancellation token in a mutable field. When a new query replaced that field, older workers could observe the new, non-cancelled token instead of the token belonging to their own query. The previous `CancellationTokenSource` was also disposed while its consumers could still be running. As stale queries accumulated, they continued invoking plugins and consuming ThreadPool workers until the process could no longer create another worker thread. ## Changes - Adds `QuerySession`, which owns one captured token and the complete task lifetime for a query. Superseded sessions are cancelled immediately and their token sources are disposed only after their work completes. - Uses a suspended session start so query state is published before workers can return results. - Adds generation checks before scheduling and applying work so superseded queries cannot enqueue stale plugin tasks or update current results. - Adds a per-plugin execution gate. Calls to the same plugin do not overlap, while unrelated plugins can execute independently; cancelled waiters do not occupy ThreadPool workers. - Preserves legacy `IResultUpdated` compatibility by correlating generation-0 events using `RawQuery`. - Preserves the original two-phase query contract: all non-delayed plugin queries complete and their results are applied before delayed queries start. Delayed queries remain globally parallel, and `noInitialResults` is computed from the complete non-delayed phase. - Cancels and performs a bounded wait for the active query during shutdown. ## Tests `Wox.Test`: **142/142 passing** locally. Coverage includes: - token ownership, cancellation, deferred disposal, shutdown timeout, and suspended session startup; - current-query generation matching and legacy generation-0 compatibility; - per-plugin execution gating and queued latest-query behavior; - deterministic verification that delayed queries cannot start until every non-delayed query completes. ## Manual validation 1. Hold a key in PowerToys Run for 10–15 seconds and confirm the PowerToys Run process thread count stabilizes instead of growing monotonically. 2. Exercise normal Calculator, file, web, and indexer queries. 3. Enable search query tuning and waiting for slow results; confirm results appear and final sorting completes. 4. Start a slow query and type again before it completes; only the newest query should update results. 5. Exit PowerToys with a query in flight; shutdown should complete cleanly without orphaned processes. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Copilot-Session: 54e1bb28-edae-496b-8211-0e1592ddc985