Build1 distinct publisher3 min readUpdated
A committed C# fix shows an attempt settling while teardown and disposal both claimed the same cancellation source. The cheap repair is ordering, not more locking.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
The reason this class of defect survives review is that nothing in it reads as wrong line by line. According to the dev.to write-up, the state on both sides of the gap is individually valid and only the ordering is wrong [7]. A reviewer who asks "is the snapshot correct" and "is the source disposed" gets yes twice, and the bug lives in the space between the two answers.
Count the exits. In the sequence described, the attempt publishes a settled snapshot to observers, completes a TaskCompletionSource, and only then clears the active task and disposes the cancellation source [5]. That is two independent ways to learn the operation is finished, both opened before ownership has moved anywhere: holding a published snapshot, and awaiting the task [1]. Both answer yes while the internal fields still say active [6]. In the generalized version, publication and TrySetResult both happen after a lock-protected block that nulls the active attempt and disposes the source [12], which takes the number of completion signals available before handoff from two to zero [1].
The mechanism worth taking is smaller than the slogan around it. The author's point is that neither Cancel nor Dispose is the load-bearing operation: it is reading the shared field and clearing it while holding the state lock, so the local variable becomes sole owner and any other path entering that lock finds null and knows it owns nothing [9]. Cancel and Dispose then run outside the lock, keeping callbacks and slow work away from protected state [10]. He is explicit that this is not an instruction to dispose everything inside a lock; only the transition has to be atomic, and what follows depends on whether disposal can block, invoke callbacks, or acquire other locks [13].
The suggested regression test only works if the ordering is already right. Block inside the observer that receives the settled snapshot, then inspect the object from another thread while publication is paused [15], checking that the previous attempt is no longer active, that a new start cannot pick up the stale completed task, that disposal does not compete for the same source, and that repeated disposal stays harmless [16]. Under the old order the observer is running inside the window, so those assertions have nothing solid to stand on. Fix the sequence and the same hook becomes a boundary test rather than a coin flip.
What the discipline buys is a lifecycle contract that can be recited: one owner at a point in time, observable completion implying released attempt state, idempotent disposal, and an immediate retry that starts new work [19]. Provenance is worth keeping in view. This is one engineer's inspection of one committed change, which applied singular cancellation-source ownership across two similar async workflows [20][2], not a survey of how often the window actually opens in production.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
An async method can finish its useful work and still be holding lifecycle state that tells the rest of the system it is active.
In the described ordering, the code builds a settled snapshot, publishes it to observers, completes a TaskCompletionSource, and only then clears the active task and disposes the cancellation source.
For a moment callers can observe 'finished' while internal state still says 'active': a retry may join the stale completed task, and concurrent disposal may capture a source the attempt is about to dispose.
These failures are rare because the state is individually valid on both sides of the gap; only the ordering is wrong.
The stated rule is that before an operation becomes observably complete, its shared lifecycle resources and active-state markers should already have reached their next valid owner or a terminal state.
The load-bearing operation is not Cancel or Dispose but the atomic transfer: reading the shared cancellation field and clearing it while holding the state lock, after which the local variable is sole owner and another path entering the same lock finds null and knows it owns nothing.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Self-contained reasoning, unverifiable artifact
The design argument is fully legible inside the article: two concrete orderings, two C# snippets, and a stated set of invariants that a reader can check by inspection. But the code is described as 'deliberately invented', the underlying committed fix is unnamed and unlinked, no failure-rate or runtime measurement is offered, and the author discloses not rerunning the test suite. One publisher, one author, no corroboration.
One unidentified commit
The only real-world application reported is a single committed change in an unnamed codebase touching two async workflows, plus its tests. There is no named project, release, dependent, download or usage disclosure, and no second observer, so adoption signal is present but minimal.
Modest claims, thin verification
Framing is proportionate — the post scopes itself as a pattern ('this is not a universal instruction to dispose every resource inside a lock'), reports a cost in code and review burden, and volunteers that runtime behaviour was not verified. The small positive tilt comes from generalizing one unverifiable commit into a lifecycle rule and asserting the fix is cheap without any measurement of the race's real frequency or of the lock-under-dispose trade-off.
Practitioner authority-building, no product on sale
The material is a developer-blog post published under a consultancy-style dev.to handle, so the visible incentive is craft credibility and audience reach. No vendor product, pricing, funding or licensing interest appears in the cluster, and the author discloses the limits of their own verification, which cuts against promotional framing.
Reasoning trustworthy, sourcing single-thread
Confidence is limited by structure rather than plausibility: one publisher, one author, invented illustrative code, and an anonymous commit. The concurrency argument itself is internally consistent and standard, and the article's caveats are unusually explicit, so the pattern-level claims can be relied on more than the incident-level ones.
build
EF Core's defaults pass code review, then meet the second replica1 distinct publisher
build
Sixteen wrong comments, one shape: rot is a category, not a fog1 distinct publisher
build
Native AOT buys a quarter-second of startup for 30MB and your reflection1 distinct publisher
build
Three manual interventions in a month, and every guard was working as designed1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 22, 2026