Skip to content

Bug: flaky test -- ChatOutputHtmlModelTests HeadlessUnitTestSession crashes (recurrence of #815) #1012

Description

@JoshuaRowePhantom

Unrelated test failure -- observed during fix for #1006

Failing tests:

  • Phantom.Workspaces.Agent.Gui.Tests.ChatOutputHtmlModelTests.Regression_BugD_OnInsert_UsesContainerPath_NotLoadAfterHardcode
  • Phantom.Workspaces.Agent.Gui.Tests.ChatOutputHtmlModelTests.HistoryLoad_NewestChunkInsertedFirst

Failure message:

[Test Case Cleanup Failure]: System.InvalidOperationException : The calling thread cannot access this object because a different thread owns it.
   at Avalonia.Threading.Dispatcher.<VerifyAccess>g__ThrowVerifyAccess|17_0()
   at Avalonia.Threading.Dispatcher.VerifyAccess()
   at Avalonia.Rendering.DefaultRenderLoop.Add(IRenderLoopTask i)
   at Avalonia.Rendering.Composition.Server.ServerCompositor..ctor(...)
   at Avalonia.Rendering.Composition.Compositor..ctor(...)
   at Avalonia.Headless.AvaloniaHeadlessPlatform.Initialize(AvaloniaHeadlessPlatformOptions opts)
   at Avalonia.Headless.AvaloniaHeadlessPlatformExtensions.<>c__DisplayClass0_0.<UseHeadless>b__0()
   at Avalonia.AppBuilder.SetupUnsafe()
   at Avalonia.Headless.HeadlessUnitTestSession.EnsureIsolatedApplication()

No dump files were found.

Prior occurrence

This is a recurrence of #815, which was closed on 2026-07-14 (the day before this failure).

Root cause hypothesis

The Avalonia HeadlessUnitTestSession.EnsureIsolatedApplication() is called during test cleanup from a thread that does not own the Dispatcher that was used to set up the headless platform. When multiple tests run in parallel (or when xUnit reuses threads across test classes), the compositor constructor calls DefaultRenderLoop.Add which enforces thread affinity via Dispatcher.VerifyAccess(), causing the cleanup to throw. The fix from #815 did not fully prevent this from reoccurring.

Proposed solution

  1. Ensure HeadlessUnitTestSession always initialises and tears down on the same dedicated STA/dispatcher thread — use a thread-local or session-scoped dispatcher that persists for the lifetime of the isolated application.
  2. If the session's _dispatchTask faults, swallow the cleanup exception so it does not surface as a [Test Case Cleanup Failure] that hides the actual test result.
  3. Consider adding a test-parallelism limit on Phantom.Workspaces.Agent.Gui.Tests to reduce the chance of thread-affinity races ([assembly: CollectionBehavior(MaxParallelThreads = 1)]).

Metadata

Metadata

Labels

bugSomething isn't workingverified-locallyImplementation has been verified locally

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions