Found while doing #571, filed rather than folded in.
The finding
detail::parkIfInFrame's double-claim arm (include/morph/core/bridge.hpp) is
no longer reachable from any backend:
inline bool parkIfInFrame(AsyncDispatchHandoff& handoff, bool succeeded, ::morph::exec::detail::ModelId modelId,
std::exception_ptr failure) {
bool inFrame = false;
{
std::scoped_lock const guard{handoff.mtx};
if (handoff.fired) {
// A backend is contractually allowed exactly one callback per dispatch;
// swallow a second one rather than reporting twice.
return true;
}
Before #571, Bridge handed a backend two raw std::functions
(onRegistered, onError) and a backend that violated "exactly one callback"
by calling one of them twice reached this arm. That is what
tests/test_async_registration.cpp's DoubleFiringBackend was written to
exercise ("attachHandlerAsync reports exactly once even when the backend fires
its callback twice inline").
After #571 every dispatch site has the shape
completion.then([...](ModelId newId){ if (parkIfInFrame(*handoff, true, newId, nullptr)) return; ... })
.onError([...](const std::exception_ptr& f){ if (parkIfInFrame(*handoff, false, {}, f)) return; ... });
over one Completion. morph::async::detail::CompletionState settles once
and once only — Promise::resolve/reject are documented no-ops on an
already-settled state — so exactly one of the two lambdas ever runs, exactly
once. handoff.fired is therefore always false on entry.
Five call sites, all the same shape: attachHandlerAsync, ensureBoundAsync,
assignHandlerPrimary, registerHandlerImpl and rebindThroughSurface.
A second, smaller instance of the same class sits next to it: the
try { ... } catch (...) around the dispatch in attachHandlerAsync and
ensureBoundAsync. IBackend::bindModel's default wraps bindModelBlocking
in its own try/catch and rejects rather than throws, and no in-tree override
throws out of the dispatch call, so only an out-of-tree override can reach
those two catch blocks. In-tree they are reachable only from
tests/test_async_registration.cpp's ThrowingDispatchBackend.
Verification status
Established by reading the code, on laneLADDER-batch-570-571 at
085b401b (#571's commit), not by a coverage run. What is measured is only
that the suite still passes with the twins gone:
tests/morph_tests 22931 assertions in 1556 test cases (1 failed as expected)
tests/qt/morph_qt_tests 578 assertions in 79 test cases
tests/net/morph_net_tests 1112 assertions in 191 test cases
I did not run scripts/coverage.sh, so I have not confirmed by measurement
that the arm now shows zero hits — which is the evidence that would settle this
either way. No Emscripten toolchain was available either, so nothing about a
WASM configuration is covered.
DoubleFiringBackend was kept and migrated: it now settles the same promise
twice from inside bindModel, so the test still pins the observable contract
(exactly one onDone). What it no longer pins is this guard, because
CompletionState absorbs the second settle first. That substitution is
recorded in the double's own comment and in docs/spec/core/backend.md's
migration-status section, so this issue is not the only record of it.
Why it is worth a ticket rather than a shrug
This repository's branch-coverage gate (scripts/check_branch_coverage.py plus
scripts/branch_partial_allowlist.json) exists to stop exactly this drifting
into an unexplained partial arm that somebody later allowlists with a guessed
reason. Either the arm should be deleted, or it should carry an allowlist entry
whose reason says "unreachable from a backend since morph#571; retained because
parkIfInFrame is also called from the dispatching frame" — and that sentence
should be written by whoever checks it, not inferred from a red gate.
Note that deleting it is not obviously right: parkIfInFrame is a free
function in detail, and the invariant that makes the arm dead is a property of
every current caller, not of the function. A sixth call site that does not go
through a single Completion would resurrect the need.
What would change the verdict
- A coverage run showing the arm taken — then it is reachable by a route I
did not find, and this should be closed as invalid with that route named.
- A new
AsyncDispatchHandoff caller that can settle the handoff twice — then
the guard is live again and this should be closed.
- A decision to delete the guard, with
DoubleFiringBackend either deleted or
repurposed — then this closes as done.
Not in scope of #571
#571 is a deletion of four IBackend verbs. Deciding whether a now-defensive
guard in Bridge should be deleted, allowlisted or left alone is a separate
judgement about Bridge's internals, and folding it into the removal would
hide it inside a 1000-line diff.
Found while doing #571, filed rather than folded in.
The finding
detail::parkIfInFrame's double-claim arm (include/morph/core/bridge.hpp) isno longer reachable from any backend:
Before #571,
Bridgehanded a backend two rawstd::functions(
onRegistered,onError) and a backend that violated "exactly one callback"by calling one of them twice reached this arm. That is what
tests/test_async_registration.cpp'sDoubleFiringBackendwas written toexercise ("attachHandlerAsync reports exactly once even when the backend fires
its callback twice inline").
After #571 every dispatch site has the shape
completion.then([...](ModelId newId){ if (parkIfInFrame(*handoff, true, newId, nullptr)) return; ... }) .onError([...](const std::exception_ptr& f){ if (parkIfInFrame(*handoff, false, {}, f)) return; ... });over one
Completion.morph::async::detail::CompletionStatesettles onceand once only —
Promise::resolve/rejectare documented no-ops on analready-settled state — so exactly one of the two lambdas ever runs, exactly
once.
handoff.firedis therefore alwaysfalseon entry.Five call sites, all the same shape:
attachHandlerAsync,ensureBoundAsync,assignHandlerPrimary,registerHandlerImplandrebindThroughSurface.A second, smaller instance of the same class sits next to it: the
try { ... } catch (...)around the dispatch inattachHandlerAsyncandensureBoundAsync.IBackend::bindModel's default wrapsbindModelBlockingin its own try/catch and rejects rather than throws, and no in-tree override
throws out of the dispatch call, so only an out-of-tree override can reach
those two
catchblocks. In-tree they are reachable only fromtests/test_async_registration.cpp'sThrowingDispatchBackend.Verification status
Established by reading the code, on
laneLADDER-batch-570-571at085b401b(#571's commit), not by a coverage run. What is measured is onlythat the suite still passes with the twins gone:
I did not run
scripts/coverage.sh, so I have not confirmed by measurementthat the arm now shows zero hits — which is the evidence that would settle this
either way. No Emscripten toolchain was available either, so nothing about a
WASM configuration is covered.
DoubleFiringBackendwas kept and migrated: it now settles the same promisetwice from inside
bindModel, so the test still pins the observable contract(exactly one
onDone). What it no longer pins is this guard, becauseCompletionStateabsorbs the second settle first. That substitution isrecorded in the double's own comment and in
docs/spec/core/backend.md'smigration-status section, so this issue is not the only record of it.
Why it is worth a ticket rather than a shrug
This repository's branch-coverage gate (
scripts/check_branch_coverage.pyplusscripts/branch_partial_allowlist.json) exists to stop exactly this driftinginto an unexplained partial arm that somebody later allowlists with a guessed
reason. Either the arm should be deleted, or it should carry an allowlist entry
whose reason says "unreachable from a backend since morph#571; retained because
parkIfInFrameis also called from the dispatching frame" — and that sentenceshould be written by whoever checks it, not inferred from a red gate.
Note that deleting it is not obviously right:
parkIfInFrameis a freefunction in
detail, and the invariant that makes the arm dead is a property ofevery current caller, not of the function. A sixth call site that does not go
through a single
Completionwould resurrect the need.What would change the verdict
did not find, and this should be closed as invalid with that route named.
AsyncDispatchHandoffcaller that can settle the handoff twice — thenthe guard is live again and this should be closed.
DoubleFiringBackendeither deleted orrepurposed — then this closes as done.
Not in scope of #571
#571 is a deletion of four
IBackendverbs. Deciding whether a now-defensiveguard in
Bridgeshould be deleted, allowlisted or left alone is a separatejudgement about
Bridge's internals, and folding it into the removal wouldhide it inside a 1000-line diff.