Skip to content

Fix headless session startup and disposal races - #22222

Merged
MrJul merged 3 commits into
AvaloniaUI:mainfrom
PrzemyslawKlys:fix/headless-session-lifetime
Sep 15, 2026
Merged

MrJul merged 3 commits into
AvaloniaUI:mainfrom
PrzemyslawKlys:fix/headless-session-lifetime

Conversation

@PrzemyslawKlys

Copy link
Copy Markdown
Contributor

What does the pull request do?

Fix two races when immediately disposing a headless test session. The worker can capture its own task before StartNew assigns it, causing a NullReferenceException during disposal. Completing the work queue before the worker exits can also cause BlockingCollection.Take to throw.

Create the worker task before starting it, and wait for it to exit before completing the queue in both disposal methods. No public API changes.

Validation

The first commit adds repeated immediate-disposal tests for synchronous and asynchronous shutdown. Both failed against the baseline; the second commit fixes them. Full NUnit per-test and per-assembly headless suites each passed 85 tests with one skipped; both xUnit suites each passed 80 tests with one skipped. A separate read-only review found no actionable defects.

This change was prepared with AI assistance.

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0069960-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@cla-avalonia

cla-avalonia commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator
  • All contributors have signed the CLA.

@PrzemyslawKlys

Copy link
Copy Markdown
Contributor Author

@cla-avalonia agree

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested, works as expected. LGTM!

@MrJul
MrJul added this pull request to the merge queue Sep 15, 2026
Merged via the queue into AvaloniaUI:main with commit 52bbf75 Sep 15, 2026
9 checks passed
MrJul added a commit to MrJul/Avalonia that referenced this pull request Sep 22, 2026
* Test immediate synchronous and asynchronous headless session disposal

* Initialize headless dispatch task before starting and stop it before queue completion

---------

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
@MrJul MrJul added backported-12.1.x and removed backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Sep 22, 2026
countincognito added a commit to countincognito/Zametek.ProjectPlan that referenced this pull request Sep 26, 2026
…hine.

Now and then the Graphs suite failed on Linux with every test passing: xUnit reported a cleanup failure of the "Headless rendering" collection, a NullReferenceException thrown from HeadlessUnitTestSession.Dispose. Avalonia 12.1.1's StartNew hands the new session its dispatch task through a variable that the dispatch thread can read before StartNew has assigned it. When that thread gets there first, the session holds no task, and Dispose throws where it waits for it, after it has already stopped the dispatch loop. It takes a busy machine, and CI no longer tolerates a failing test step, so it would have failed CI at random. The fixture now tolerates exactly that state, which it recognises by reading the session's private _dispatchTask, so any other failure to dispose still fails the run. Avalonia fixed the race in 12.1.3 (AvaloniaUI/Avalonia#22222), and TODO.md records moving to it and deleting the guard.

Under WSL, with both cores kept busy and two suites running at a time, 5 of 30 runs failed, each of them a session started without its task. With the guard, 30 of 30 passed although 7 sessions started without their task, and 20 of 20 passed on the fixture as committed. Windows never hit the race in 30 runs under the same kind of load, and all suites pass there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
countincognito added a commit to countincognito/Zametek.ProjectPlan that referenced this pull request Sep 26, 2026
… headless session guard it replaces.

Avalonia 12.1.3 fixes the race in HeadlessUnitTestSession.StartNew (AvaloniaUI/Avalonia#22222) that ceb80d1 guarded against in the Graphs tests' fixture, so the guard, its comment and its TODO.md entry go. Every Avalonia package moves to 12.1.3 together, with Avalonia.Controls.DataGrid and ReactiveUI.Avalonia at 12.1.2, Dock at 12.1.0.6 and Xaml.Behaviors at 12.0.7, and alongside them Autofac 9.3.4, the Microsoft.Extensions and System packages at 10.0.12, MPXJ.Net 16.9.0, Microsoft.NET.Test.Sdk 18.10.1 and the Windows SDK build tools 10.0.28000.2705.

Svg.Skia stays at 5.1.1 and Svg.Controls.Skia.Avalonia at 12.0.0.13, with a comment on each saying why. From Svg.Skia 5.2.0 and Svg.Controls.Skia.Avalonia 12.0.0.14 on, they require SkiaSharp 4.148, which NuGet then resolves for the whole solution, although Avalonia and ScottPlot are still built against SkiaSharp 3.119 and the native libraries pinned for Linux and the browser stay at 3.119.4. On Linux every draw failed, SkiaSharp refusing the version 119 native library in 27 Graphs tests, and the build gained 11 obsolete-API warnings.

Under the same CPU load that made the race hit 5 to 7 times in 30 runs of the Graphs suite on 12.1.1, the unguarded fixture now passes 30 of 30, every session starting with its dispatch task. All suites pass on Windows and on Linux with no new warnings, the desktop app was smoke-tested, and zpp's outputs are unchanged: 247 of 250 came out byte-identical, and the three MS Project imports differ only in the day they were run on. Dock.Model.ReactiveUI 12.1.0.6 and DynamicData 9.4.33 still depend on System.Reactive, so it stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
grokys pushed a commit to Evan260/Avalonia that referenced this pull request Oct 7, 2026
* Test immediate synchronous and asynchronous headless session disposal

* Initialize headless dispatch task before starting and stop it before queue completion

---------

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants