[v1.5] Cherry-pick #8182: wrap outer SynchronizationContext in ActorCellKeepingSynchronizationContext#8200
Merged
Aaronontheweb merged 1 commit intoMay 8, 2026
Conversation
…tionContext (akkadotnet#8182) * [xUnit 3] Fix parallel-class implicit-sender leak in Akka.TestKit.Xunit Under xUnit v3 parallel-class scheduling, MaxConcurrencySyncContext dispatches test ctors and bodies onto a dedicated pool of reused threads. TestKitBase's constructor pins InternalCurrentActorCellKeeper.Current (a ThreadStatic) on its ctor thread; when a sibling test's body lands on that same reused thread, the pre-await synchronous prefix of the [Fact] reads the sibling's cell as its implicit sender, causing Tell() to use the wrong Sender. Replies then cross ActorSystem boundaries. Fix: add a BeforeAfterTestAttribute that runs synchronously on the test- body thread and (a) pins Current to the running test's TestActor cell, (b) installs ActorCellKeepingSynchronizationContext so await continuations also re-pin the cell. After the test, Current is cleared so the reused worker doesn't carry the cell into the next test. Applied to Akka.TestKit.Xunit.TestKit so derived test classes (including Akka.Hosting.TestKit downstream) get parallel-safe behavior automatically via attribute inheritance. ActorCellKeepingSynchronizationContext promoted to [InternalApi] public so the attribute can install an equivalent wrapper without duplicating the save/pin/restore logic. TBD XML docs replaced with real documentation. Regression tests: - ParallelAmbientContextSpec: 16 + 8 sibling test classes asserting implicit-sender correctness and INoImplicitSender == null across awaits. Marked [LocalFact] — requires xUnit.ParallelizeTestCollections=true to exercise the bug (disabled in the shared src/xunit.runner.json). - AkkaCleanAmbientContextAttributeSpec: reflection guards that the attribute is declared on the base TestKit, is inherited by subclasses, and has Inherited=true in its AttributeUsage. * Update API approval list * Keep xUnit ambient context plumbing internal Keep ActorCellKeepingSynchronizationContext internal via friend assembly access and run Akka.TestKit.Xunit.Tests with parallel collections so CI exercises the implicit-sender leak regression by default. * Remove exception-driven ambient context checks Stop swallowing NullReferenceException when reading TestActor and drop the reflection-only attribute spec in favor of the parallel behavior tests that now run in CI. * fix: wrap outer SynchronizationContext instead of replacing it ActorCellKeepingSynchronizationContext now accepts an optional inner SynchronizationContext and delegates Post/Send scheduling to it while wrapping callbacks with the cell-pinning save/restore window. When no inner SC exists (the default), behavior is identical to before. AkkaCleanAmbientContextAttribute captures the active SC in Before() and passes it as the inner SC, then restores it in After(). This preserves xUnit v3's MaxConcurrencySyncContext scheduling, which downstream consumers like Akka.Hosting.TestKit depend on for async IHost lifecycle. Without this, applying the attribute to Hosting's TestKit causes test hangs. See akkadotnet/Akka.Hosting#735 and akkadotnet/Akka.Hosting#733. * fix: use AsyncLocal instead of ThreadStatic in BeforeAfterTestAttribute xUnit v3's runner awaits the test body between Before() and After(), so After() can resume on a different OS thread. ThreadStatic fields set in Before() are invisible on the new thread, causing After() to silently skip cleanup. AsyncLocal flows via ExecutionContext across await boundaries, ensuring correct save/restore regardless of thread. * fix: harden xUnit ambient context reuse * refactor: convert AmbientContextState to record Aligns with project style guidance (sealed classes and records as default for immutable data carriers). Behavior unchanged — instance is only stored/retrieved through AsyncLocal, never compared. Per review feedback on PR akkadotnet#8182. --------- Co-authored-by: Gregorius Soedharmo <arkatufus@yahoo.com> (cherry picked from commit 515a266)
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.
Cherry-pick of #8182 from
devtov1.5.Summary
Akka.TestKit.Xunitwhere reused worker threads caused sibling tests to read the wrongInternalCurrentActorCellKeeper.Currentvalue as their implicit sender.AkkaCleanAmbientContextAttribute(aBeforeAfterTestAttribute) that pins the running test'sTestActorcell viaAsyncLocaland installsActorCellKeepingSynchronizationContextto wrap (not replace) the outer SC, preserving xUnit v3'sMaxConcurrencySyncContextscheduling that downstream consumers likeAkka.Hosting.TestKitdepend on.ActorCellKeepingSynchronizationContextnow accepts an optional innerSynchronizationContextand delegatesPost/Sendto it while still wrapping callbacks with the cell-pinning save/restore window.ParallelAmbientContextSpecandActorCellKeepingSynchronizationContextSpec.Akka.Hosting.TestKitimplicit-sender leaks acrossActorSystems under xUnit v3 parallel class execution Akka.Hosting#733.Conflict resolution
The two
CoreAPISpec.ApproveTestKit.*.verified.txtfiles conflicted because v1.5's API approval baseline uses the long-form attribute names (AssemblyMetadataAttribute,ComVisibleAttribute, etc.) whiledevuses the short form. Kept v1.5's existing style and only added the new[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.TestKit.Xunit")]line in alphabetical position. API approval tests pass locally on net10.0.Test plan
Akka.TestKit.Xunitsucceeds (Release)Akka.API.TestsApproveTestKit specs pass on net10.0