Skip to content

test(xpp): verify .await() inside spawned fns + correct spawn.h docs - #86

Merged
mivinci merged 1 commit into
mainfrom
codebuddy/spawn-await-tests
Aug 26, 2026
Merged

mivinci merged 1 commit into
mainfrom
codebuddy/spawn-await-tests

Conversation

@mivinci

@mivinci mivinci commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

Empirically settles the "can you .await() inside xpp::spawn?" question with tests, and corrects the spawn.h docs that said it "would block unless already inside a fiber" — which was misleading.

Verified semantics (4 new tests)

Test Verifies
AwaitInsideSpawnedFn A spawned fn .await()s a channel recv; a timer feeds the channel while the fn is parked — park() re-enters the event loop, the world keeps turning
SequentialAwaitsInsideSpawnedFn Two park/resume cycles in one fn (each .await() parks, gets fed by its own timer, resumes)
AwaitTimerInsideSpawnedFn .await() on a timer promise directly
AwaitCrossChainInsideSpawnedFn Chain A awaits a promise that chain B resolves after a timer — cross-chain .await(), both chains spawned on the same loop

Doc correction (spawn.h)

The old text discouraged .await() inside spawned fns. The corrected doc states:

  • Usable: parks the fn's stack while re-entering the event loop (park() runs X_RUN_ONCE until woken) — timers, I/O, other chains keep progressing
  • Idiomatic forms remain .then() chains (C++11) / co_await (C++20) — waker-driven, no parked stack — but plain sequential .await() works when convenient
  • Deadlock boundary documented: awaiting anything that only resolves after the fn returns (e.g. the spawn's own JoinHandle) is a circular wait and hangs — not tested here since it would hang CI

Context: this came out of evaluating a WaitScope-fiber design (make .await() a true suspension via a loop fiber). The design was explored and rejected — the re-entrant park model already delivers the observable semantics; the fiber design's costs (livelock traps, lifecycle dance) outweighed its aesthetic benefit. The tests + doc correction are the durable output of that exploration.

Verification

  • spawn_test 14/14 (ASan, 4 new)
  • TSan: 0 warnings
  • Related promise suites green

Empirically settles the 'can you .await() inside xpp::spawn?' question
that came up while evaluating a WaitScope fiber design (explored and
rejected — the re-entrant park model already delivers the observable
semantics; see the deleted todos/wait-scope-fiber-await.md discussion).

Four new tests in spawn_test.cpp:

- AwaitInsideSpawnedFn — a spawned fn .await()s a channel recv; a timer
  feeds the channel while the fn is parked. Verifies park() re-enters
  the event loop: the world keeps turning while the fn waits.
- SequentialAwaitsInsideSpawnedFn — two park/resume cycles in one fn
  (each .await() parks, gets fed by its own timer, resumes).
- AwaitTimerInsideSpawnedFn — .await() on a timer promise directly.
- AwaitCrossChainInsideSpawnedFn — chain A awaits a promise that chain
  B resolves after a timer: cross-chain .await() with both chains
  spawned on the same loop.

The old spawn.h doc said .await() inside a spawned fn 'would block
unless already inside a fiber' — misleading. Corrected to state the
verified semantics: usable (parks the step's stack while re-entering
the loop), idiomatic forms remain .then()/co_await, and documenting
the real deadlock boundary (awaiting anything that only resolves
after the fn returns, e.g. its own JoinHandle — circular wait).

Verified: spawn_test 14/14 (ASan), TSan 0 warnings, related promise
suites green.
@mivinci
mivinci merged commit 4504c83 into main Aug 26, 2026
19 of 20 checks passed
@mivinci
mivinci deleted the codebuddy/spawn-await-tests branch August 26, 2026 02:55
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.

1 participant