Structured concurrency policies
Latest stable Based on Bluetape4k release 1.11.0
Child-failure handling is part of the final result semantics. Decide whether every branch is required, one success is enough, or partial data remains valid.
Choose the policy
Section titled “Choose the policy”| Business result | API | Child failure | Read results with |
|---|---|---|---|
| atomic: all required | taskScope / failFastTaskScope | cancel unfinished siblings | join().throwIfFailed(), then get() |
| any: one success is enough | firstSuccessTaskScope | keep candidates until winner | join().result(mapper) |
| partial: independent results | supervisedTaskScope | siblings continue | results(), successfulResults(), failedExceptions() |
All helpers execute a StructuredTaskScope through the virtual-thread dispatcher bridge. Caller cancellation must not leave orphan tasks beyond the scope boundary.
Fail-fast atomic composition
Section titled “Fail-fast atomic composition”suspend fun loadCheckout(id: String): Checkout = taskScope("checkout") { val cart = fork { cartClient.load(id) } val price = fork { pricingClient.calculate(id) } val stock = fork { inventoryClient.reserve(id) }
join().throwIfFailed() Checkout(cart.get(), price.get(), stock.get())}One failure invalidates the whole checkout. Do not read subtasks before throwIfFailed() or swallow individual failures.
First-success fallback
Section titled “First-success fallback”suspend fun resolveAddress(query: String): Address = firstSuccessTaskScope { fork { primary.resolve(query) } fork { secondary.resolve(query) } fork { archive.resolve(query) } join().result { cause -> AddressNotFound(query, cause) }}The first failure is not the final failure. The mapper runs only when all providers fail. Resolve idempotency before racing side-effecting providers.
Supervised partial results
Section titled “Supervised partial results”suspend fun loadWidgets(userId: String): List<Result<Widget>> = supervisedTaskScope<Widget, List<Result<Widget>>>("widgets") { fork { recommendations.load(userId) } fork { messages.load(userId) } fork { promotions.load(userId) } join() results() }Supervision is not failure suppression. Define how failed widgets appear in the response and which exceptions are recorded in metrics and logs.
Async bridge
Section titled “Async bridge”CoroutineScope.asyncTaskScope and asyncSupervisedTaskScope return a Deferred immediately. Use them to compose multiple structured scopes in a larger coroutine operation. The receiver scope owns the returned deferred.
Verification checklist
Section titled “Verification checklist”- sibling state after failure matches the chosen policy;
- first-success chooses a success rather than the first failure;
- all-provider failure maps to a domain exception;
- supervised results expose successes and failures;
- no unfinished task remains after parent cancellation or deadline;
- deployment JDK/runtime supports the virtual-thread bridge.
Source and representative tests
Section titled “Source and representative tests”StructuredConcurrency.ktStructuredConcurrencyTest.kt- Core virtual-thread scope implementations live in the
bluetape4k-coreconcurrency package.
Continue with Operations and observability to operate and shut down the selected policy.