Kotlin Coroutines
Treat cancellation, ownership, and lifecycle as public contracts, not implementation details.
Core rules
- Every launched coroutine must have an owner whose lifetime is explicit. Avoid
GlobalScope. - Prefer
coroutineScopefor fail-together work andsupervisorScopeonly when sibling failure isolation is required by contract. - Re-throw
CancellationException; broad exception handlers must not consume cancellation. - Move blocking calls to an appropriate dispatcher or replace them with cancellable asynchronous APIs.
- Do not expose a cold
Flowas if it were shared state. Choose cold Flow,StateFlow, orSharedFlowfrom subscriber and replay semantics. - Bound channels and queues. Define overflow, fairness, ordering, and shutdown behavior.
- Preserve context intentionally; do not pass request-scoped mutable state through globals or thread locals.
- Make cleanup run on success, failure, timeout, and cancellation. Use
finallyand cancellable resource APIs.
Review sequence
- Map coroutine creation sites to scope owners.
- Trace cancellation from caller through child work and adapters.
- Identify blocking calls and dispatcher assumptions.
- Record ordering, buffering, retry, timeout, and supervision contracts.
- Write deterministic tests using virtual time or event-driven synchronization.
- Stress lifecycle transitions and repeated cancellation.
Testing expectations
Assert emitted values, ordering, terminal errors, cancellation cleanup, no post-cancel work, and resource closure. Avoid fixed sleeps and tests that only assert completion.
./gradlew --no-daemon test
./gradlew --no-daemon check