Kotlin / Editorial solution guide
Diagnose ignored cancellation and missing state updates
Separate swallowed cancellation, unowned coroutine lifetimes, cold-flow collection, and StateFlow conflation.
Read-only knowledge base · No public comments
Recognize the symptoms
A fictional screen continues work after it closes, a flow appears to do nothing, and a slow observer misses intermediate state changes. All are described as coroutine bugs, but each symptom suggests a different contract. Inspect lifetime, collection, and delivery semantics separately before replacing dispatchers or adding delays.
Understand the likely causes
- Candidates include a scope that outlives its owner, a broad catch converting CancellationException into success, a cold flow with no collector, in-place mutation inside equality-conflated state, or an event stream incorrectly represented as current state. A noncooperative blocking operation can also delay cancellation.
Trace who creates and cancels the scope
Record the owning request, screen, or application component, the scope it creates, and the event that ends that lifetime. A global or freshly detached scope can bypass the caller’s cancellation even when every function is marked suspend. Inspect the parent Job relationship and any explicit replacement context. Do not patch ownership with arbitrary sleep calls. If work must survive the screen, give it a deliberate application or durable workflow owner and a product-visible outcome rather than allowing it to continue accidentally.
Preserve the cancellation signal through translation
Search the relevant boundary for broad catches, runCatching-like helpers, and fallback values that convert all failures into ordinary data. The fragment below deliberately rethrows cancellation first. It is not a complete application and requires a compatible kotlinx.coroutines context. Next inspect long loops and blocking calls that never suspend or check the active context. Cancellation is cooperative, so changing a dispatcher alone may relocate an unresponsive operation without making it cancellable. A timeout also cannot establish that an external effect was undone.
try {
loadOwnedData()
} catch (cancelled: kotlinx.coroutines.CancellationException) {
throw cancelled
} catch (failure: ExpectedLoadFailure) {
showDeclaredFailure(failure)
}
The names represent application-specific operations and error types, so this fragment is non-runnable. It illustrates narrow failure translation rather than a verified implementation.
Check whether values are produced, collected, or conflated
A cold flow starts its producer when collected, so creating a pipeline can correctly produce no immediate work. StateFlow is different: it has current state and equality-based conflation, and a slow subscriber may skip intermediate values. If every reservation event matters, use a delivery design that preserves those event semantics instead of expecting a state holder to act as a durable log. For state objects, avoid mutating a shared nested list and assigning the same effective value. Publish a new value under a documented owner and keep update lambdas free of external effects.
Question: producer never starts
Inspect: cold Flow has an owned terminal collector
Question: intermediate states are skipped
Inspect: StateFlow equality/conflation and observer speed
Question: work survives owner cancellation
Inspect: Job parentage, broad catches, and cooperative checks
The map identifies distinct hypotheses. It contains no observed runtime result, dispatcher tuning value, or guarantee that every symptom has only one cause.
Verify with explicit lifecycle events and controlled scheduling
Use an isolated project with its declared Kotlin and coroutine test tooling. Start owned work, trigger cancellation, and wait for completion before asserting cleanup. Collect the cold flow and inspect its expected values. For StateFlow, assert current-state semantics rather than demanding every intermediate event from a slow collector. Use the test framework’s supported scheduler instead of wall-clock sleep assumptions where appropriate. Record library versions and actual results; this guide has no compiler or coroutine runtime and therefore reports a proposed plan only.
Verify the fix
A correct fix demonstrates owner cancellation, preserved cancellation signals, and the intended stream delivery contract under the configured isolated test environment. Check resource state and final business outcome as well as log order. No runtime, timing, or subscriber-race result is claimed by this guide.
Keep it from recurring
- Review every created scope, broad exception translator, shared state update, and flow-sharing decision for ownership and semantics. Separate current state from durable events, keep update functions pure, and preserve cancellation when introducing new fallback logic or provider adapters.