Skip to content

docs: correct the post-#151 QtExecutor teardown claims, and pin what dropping costs - #256

Merged
Yaraslaut merged 1 commit into
masterfrom
docs/194-qtexecutor-teardown-claims
Aug 24, 2026
Merged

docs: correct the post-#151 QtExecutor teardown claims, and pin what dropping costs#256
Yaraslaut merged 1 commit into
masterfrom
docs/194-qtexecutor-teardown-claims

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Closes #194.

The stale claims

Six comments still described QtExecutor's pre-#151 behaviour — that it is
stateless, and that callbacks it has already posted "stay safe" after their owner
is gone. Since #151 it carries an _alive token, and the guard returns before
invoking the callback body, so a queued task is dropped, not run.

docs/spec/core/executor.md was contradicting itself on its own page: §Teardown
says tasks are dropped, and forty-one lines later the QtExecutor section said it
has "no lifetime or shutdown concerns of its own".

Corrected:

site claim
docs/spec/core/executor.md "otherwise stateless … no lifetime or shutdown concerns"
examples/bookmarks/…/app.hpp "callbacks it has already posted stay safe after App is gone"
examples/pastebin/…/app.hpp same sentence, verbatim twin
examples/pastebin/…/app.hpp "they detonate whenever some later processEvents() gets to them"
examples/pastebin/src/server/main.cpp "a callback delivered after ~App is a use-after-free"
examples/common/testkit/backend_rig.hpp not listed in the issue — same "stateless … stay safe" sentence

Two sites deliberately left alone

backend_rig.hpp and examples/TESTING.md both say a stale callback "detonates
inside whatever later test pumps it". That reads like the pastebin sentence I
did remove, but it is still true: it describes a pool thread calling
post() on a freed IExecutor* — a member call on destroyed memory.
_alive guards delivery of an already-queued event; it cannot guard that, and
#151 never claimed to.

The declaration-order rationale those comments justify therefore stays fully
load-bearing, which is why only the trailing "stays safe" sentence came out of
each — not the paragraph around it.

What dropping actually costs

Documented nowhere, and observable: the decrement lives inside the
.then/.onError body, so a dropped completion releases its shared_ptr copy of
the counter without ever decrementing. _fetchInFlight / _sweepInFlight now
carry that as a stated invariant, together with why a dropped completion should
not decrement — dropping is deliberate, a decrementing hook would run
application teardown code from inside framework teardown, and no consumer can
observe the difference, since the only reader is a drain that finishes strictly
before ~App.

Pinned, not just asserted

An invariant stated only in prose is what drifted here in the first place, so
tests/qt/test_qt_executor_teardown.cpp now reproduces the apps' exact pattern.
I measured this myself rather than carrying over the issue's numbers:

  • dropped .then → counter stays 1
  • dropped .onError → counter stays 1
  • delivered → counter reaches 0

Mutation-checked: pumping before the executor dies, so the callback is
delivered rather than dropped, fails the assertion (0 == 1). The test measures
the drop, not merely a counter nobody touches.

Verification

No behaviour change — comments, spec prose, and new tests only.

check result
morph_qt_tests 503 assertions in 74 cases, all passed
pastebin + bookmarks ladder 173 / 173 passed
Doxygen (WARN_AS_ERROR) builds clean

…dropping costs

Six comments still described QtExecutor's pre-#151 behaviour: that it is
stateless, and that callbacks it has already posted "stay safe" after their
owner is gone. Since #151 it carries an `_alive` token and a queued task is
dropped, not run. docs/spec/core/executor.md was self-contradictory on its own
page -- §Teardown says tasks are dropped, and forty-one lines later the
QtExecutor section said it has "no lifetime or shutdown concerns of its own".

Fixed: executor.md's stateless claim; the "stays safe" sentence in bookmarks'
and pastebin's app.hpp; pastebin's "they detonate whenever some later
processEvents() gets to them"; and main.cpp's "a callback delivered after ~App
is a use-after-free" -- after ~App there is no delivery at all.

One site more than #194 lists: examples/common/testkit/backend_rig.hpp carried
the same "QtExecutor is stateless ... callbacks stay safe after the rig is gone"
sentence. It is stale for the same reason and is corrected here.

Two superficially identical sites are deliberately left alone, because they are
still true: backend_rig.hpp's and TESTING.md's "the stale callback ... detonates
inside whatever later test pumps it" describe a pool thread calling post() on a
*freed IExecutor\**, a member call on destroyed memory. `_alive` guards delivery
of an already-queued event; it cannot guard that, and #151 did not claim to. The
declaration-order rationale those comments justify stays load-bearing, so only
the trailing "stays safe" sentence was removed from each.

The consequence of dropping was documented nowhere and is observable: the
decrement lives inside the .then/.onError body, and the `_alive` guard returns
before invoking it, so a dropped completion releases its shared_ptr copy of the
counter without decrementing. bookmarks' `_fetchInFlight` and pastebin's
`_sweepInFlight` now state that as an invariant, along with why a dropped
completion should *not* decrement: dropping is deliberate, a decrementing hook
would run app teardown code from inside framework teardown, and no consumer can
observe the difference -- the only reader of the counter is a drain that
finishes strictly before ~App.

Rather than leave that invariant as unguarded prose, tests/qt/
test_qt_executor_teardown.cpp now pins it, reproducing the apps' exact pattern.
Verified independently rather than taken from the issue: dropped .then and
dropped .onError both leave the counter at 1, a delivered completion takes it to
0. Mutation-checked -- pumping before the executor dies, so the callback is
delivered instead of dropped, fails the assertion (0 == 1), so it measures the
drop and not merely a counter nobody touches.

No behaviour change. morph_qt_tests 503 assertions / 74 cases; pastebin+bookmarks
ladder 173/173; Doxygen builds clean.

Closes #194
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Yaraslaut
Yaraslaut merged commit 6198b8c into master Aug 24, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App docs claim QtExecutor callbacks still run after ~App; since #151 they are dropped, and a dropped completion never decrements fetchInFlight

1 participant