Skip to content
Bluetape4k docs1.11

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.

Comparison of taskScope, firstSuccessTaskScope, and supervisedTaskScope

Business resultAPIChild failureRead results with
atomic: all requiredtaskScope / failFastTaskScopecancel unfinished siblingsjoin().throwIfFailed(), then get()
any: one success is enoughfirstSuccessTaskScopekeep candidates until winnerjoin().result(mapper)
partial: independent resultssupervisedTaskScopesiblings continueresults(), successfulResults(), failedExceptions()

All helpers execute a StructuredTaskScope through the virtual-thread dispatcher bridge. Caller cancellation must not leave orphan tasks beyond the scope boundary.

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.

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.

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.

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.

  1. sibling state after failure matches the chosen policy;
  2. first-success chooses a success rather than the first failure;
  3. all-provider failure maps to a domain exception;
  4. supervised results expose successes and failures;
  5. no unfinished task remains after parent cancellation or deadline;
  6. deployment JDK/runtime supports the virtual-thread bridge.

Continue with Operations and observability to operate and shut down the selected policy.