Skip to content

tests: #550's cancellation guarantees are asserted by nothing that runs, and the probes that measured them were not landed #622

Description

@Yaraslaut

Found while running #550's Part 1. Filed rather than left as an aside inside that issue's comments, per AGENTS.md — "File, don't fold. An aside inside a closed issue lapses unless it gets its own issue."

The finding

#550's Part 1 produced a table of 27 cancellation entry points and measured their guarantees on a five-point scale (G0 nothing … G4 no callback still running). Four of those rows were reproduced by standalone probe programs, and two of the reproductions were sharp enough to become defects in their own right:

Those probe programs are gone. They lived in a lane scratchpad and were removed with the worktree. So the guarantees they proved are now recorded as prose on #550 and asserted by nothing that runs.

Why that matters here specifically

The whole point of #550's scale is the distinction between "no further callback will be scheduled" (G1/G2) and "no further callback will start" (G3). A caller who reads a G2 verb as G3 and frees the receiver on the next statement has a use-after-free — and docs/spec/concurrency_and_lifetimes.md:164 says the rules there "encode recent fixes to real deadlocks and use-after-frees", so this is a class of bug this repository has already shipped and fixed at least once.

A guarantee that nothing asserts is a guarantee that can regress silently. That is AGENTS.md's "verify rather than assert" applied to the concurrency contract itself.

Verification status: weak, and labelled as weak

Measured on master @ 0e3b8823:

$ grep -rn "cancelPending" tests/ | wc -l
38

38 call sites across 12 test files. So the verbs are exercised a great deal — this is emphatically not "cancellation is untested."

What I could not establish is whether any test asserts the strength of the guarantee rather than its effect. My probe for that was crude — searching each file for an assertion that a counter stayed at zero near a cancelPending — and it found none in any of the twelve:

tests/test_bridge_lifetime.cpp              cancelPending=7   zero-assertions=0
tests/test_coverage_push95.cpp              cancelPending=8   zero-assertions=0
tests/test_switch_backend.cpp               cancelPending=5   zero-assertions=0
tests/test_backend_extra.cpp                cancelPending=4   zero-assertions=0
tests/test_async_registration.cpp           cancelPending=3   zero-assertions=0
tests/test_backend_registration_surface.cpp cancelPending=3   zero-assertions=0
...

That heuristic is not good enough to conclude on. It matches only a few spellings of "this did not happen", and a test could assert the same property through a Catch2 matcher, a drained-queue check, or a scope-exit assertion it would never see. Do not treat "zero-assertions=0" as a measurement — treat it as the reason someone should look properly.

I did not: read any of the twelve test files, run the suite, or attempt to reconstruct the deleted probes.

What would resolve it

  1. Audit the twelve files properly and establish whether any existing test distinguishes G2 from G3. If one does, this issue shrinks to "land the remaining probes."
  2. Land the reproductions as tests/test_cancellation_policy.cpp — one case per reproduced row of research: write down the real cancellation policy, then test whether stdexec (or std::stop_token, or strand-confinement) removes any of the 54 mutexes #550's table, asserting the G-level, not just that an error arrived. research: write down the real cancellation policy, then test whether stdexec (or std::stop_token, or strand-confinement) removes any of the 54 mutexes #550's second comment proposes exactly this as a regression baseline for its Part 2.
  3. Per AGENTS.md, each case must fail if the guarantee weakens — e.g. a case for core: SynchronousBackendAdapter::cancelPending does not cancel the completions the adapter itself produced #619 asserts errRan == 1 && okRan == 0, so it fails today and passes once core: SynchronousBackendAdapter::cancelPending does not cancel the completions the adapter itself produced #619 is fixed. A case that merely asserts "something settled" would pass in both worlds and is not evidence.

The probes cannot be recovered, so step 2 means rewriting them from the table on #550, which is the durable record that survived.

What would change the verdict

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreSubsystem: corebugSomething isn't workingtriage: rescopeReal problem, wrong framing; rewrite before building

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions