Files
capod/app
darken b77f4a9581 fix(flow): Subscribe before emitting in DynamicStateFlow.updateBlocking
updateBlocking emitted its update into updateActions FIRST and only then
subscribed internalFlow.first { it.updatedBy == update } against a
shareIn(replay = 1). That is a lost wakeup: if the producer processes our
update plus a successor before the first{} collector is registered, our
identity-matched State is displaced from the replay-1 cache and the await
never completes. A reactive collector makes the successor guaranteed rather
than unlikely - RecorderModule reacts to every state with an update of its
own, so each caller's update immediately breeds another.

The trigger is scheduling pressure: on a 2-core machine the producer and the
awaiting caller no longer run in parallel, so the emit-to-subscribe window is
wide. The CI test jobs wedged at GitHub's 6h job timeout; reproduced locally
under taskset -c 0,1 and confirmed by jstack - the caller parked forever in
updateBlocking's first{} while the producer sat idle with the update long
since processed.

The awaiter is now started UNDISPATCHED before the emit, so it runs
synchronously up to its first suspension inside first's collect and the
collector exists on the shared flow before the update can be processed.

Covered by a contention test that mirrors the module interaction (two
concurrent updateBlocking workers plus a value-neutral reactive echo
collector), and the two recorder test harnesses now wrap their block and
their cleanup stop in timeouts - a regressed await has to fail in seconds
instead of wedging a runner for six hours.
2026-08-03 08:30:33 +02:00
..
2022-09-14 18:00:18 +02:00
2022-09-14 18:00:18 +02:00