chore(aot-smoke): cover handler exceptions, unsubscribe lifecycle, cancellation#78
Merged
Merged
Conversation
Three new assertion blocks in samples/ZeroAlloc.AsyncEvents.AotSmoke/Program.cs to cover Sequential exception propagation, Parallel unsubscribe/resubscribe lifecycle, and CancellationToken pre-cancellation propagation. Reuses the existing OrderService via fresh instances per block; no new fixture files. CI-only chore — no library changes, no NuGet release. Strikes B1 shipped on merge. Same pattern as ZA.Validation B4 (#51) and ZA.Inject B1 (#68). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4-phase plan mirroring ZA.Validation B4 (#51) and ZA.Inject B1 (#68): three new assertion blocks in Program.cs covering Sequential exception propagation, Parallel unsubscribe/resubscribe lifecycle, and CancellationToken pre-cancellation propagation. No new fixture files; each block reuses OrderService via fresh instances. Phase 4 strikes B1 shipped and opens the PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sequential mode bails on the first handler throw — handler2 never runs. Asserts the caught exception is InvalidOperationException (not wrapped) and that handler2's invocation count remains 0. Validates the foreach loop's first-throw-bails behavior under PublishAot.
Subscribe-invoke-unsubscribe-invoke-resubscribe-invoke flow tests the lockless CAS register/unregister path under PublishAot. Asserts count progression 1 → 1 → 2 across the three phases; catches regressions in Unregister's removal or Register's post-unregister re-add.
Pre-cancelled token surfaces OperationCanceledException via the library's ct.ThrowIfCancellationRequested() before the first handler runs. Asserts OCE is caught (not swallowed) and handler count remains 0 (ct check fires before the handler invocation, not after). Deterministic — no TCS or timing dependency.
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.
Summary
Closes backlog item B1. The existing aot-smoke project covered only the single-handler happy path for both Sequential + Parallel modes; this PR adds three independent assertion blocks exercising the three previously-uncovered behavioral paths under
PublishAot=true:InvokeAsyncrethrows → handler2 never runs. Asserts caught exception type and handler2 count = 0.Register/Unregister's lockless CAS path.OperationCanceledExceptionpropagates before any handler runs. Deterministic (no TCS / timing fragility).Why now
Surfaced 2026-05-27 during the org-wide aot-smoke coverage survey done after ZeroAlloc.Serialisation shipped 2.3.1 + 2.3.2 reactively. Same "smoke exists but partial" pattern applied to ZA.AsyncEvents. This PR closes it. Already-shipped siblings in the same workstream: ZeroAlloc.Validation B4 (#51) and ZeroAlloc.Inject B1 (#68).
What changed
Program.cs(~80 LOC total) — each block creates a freshOrderService()for isolationusing ZeroAlloc.AsyncEvents;directive (Phase 2's explicitAsyncEvent<int>local needed it)docs/backlog.md— B1 entry struck shipped with durable findings preservedNo new fixture files. No library changes.
Findings worth flagging
using ZeroAlloc.AsyncEvents;— Phase 2'sAsyncEvent<int>local needed the namespace imported; Phase 1's inline lambdas didn't.InvalidOperationExceptionsurfaces fromInvokeAsyncdirectly, not wrapped.ct.ThrowIfCancellationRequested()fires BEFORE the handler loop — confirmed by the cancel-before-invoke assertion (handler count = 0).Decisions (design doc)
OrderServicevia fresh instances rather than adding three new fixture servicesSemVer
No package version bump — CI-only
chore:changes. release-please will treat aschore:and skip the release manifest.Test plan
AOT smoke: PASS(Phases 0+1+2+3 all green)🤖 Generated with Claude Code