|
9 | 9 | package dev.restate.sdk.kotlin |
10 | 10 |
|
11 | 11 | import dev.restate.common.Slice |
| 12 | +import dev.restate.sdk.common.AbortedExecutionException |
12 | 13 | import dev.restate.sdk.endpoint.definition.HandlerContext |
13 | 14 | import dev.restate.serde.Serde |
| 15 | +import java.util.concurrent.CompletionStage |
14 | 16 | import kotlinx.coroutines.CancellationException |
| 17 | +import kotlinx.coroutines.future.await as kotlinxAwait |
| 18 | + |
| 19 | +/** |
| 20 | + * Awaits [this], translating the SDK-internal [AbortedExecutionException] control-flow signal into |
| 21 | + * a coroutine [CancellationException]. |
| 22 | + * |
| 23 | + * The state machine unwinds a suspended or closed invocation by completing the awaited future |
| 24 | + * exceptionally with [AbortedExecutionException] — a bare [Throwable] the Java SDK sneaky-throws. |
| 25 | + * Letting that raw throwable surface into coroutine code is not idiomatic: it is not a |
| 26 | + * [CancellationException], so structured concurrency treats it as a genuine failure and a user |
| 27 | + * `catch (e: Throwable)` would silently swallow the suspension instead of propagating it. Wrapping |
| 28 | + * it as [CancellationException] keeps invocation suspension behaving like ordinary coroutine |
| 29 | + * cancellation, as it did before the shared-core state machine contract. |
| 30 | + * |
| 31 | + * All SDK await sites go through this wrapper (instead of `kotlinx.coroutines.future.await`). |
| 32 | + */ |
| 33 | +internal suspend fun <T> CompletionStage<T>.await(): T = |
| 34 | + try { |
| 35 | + this.kotlinxAwait() |
| 36 | + } catch (e: AbortedExecutionException) { |
| 37 | + throw CancellationException("Restate invocation suspended or closed").apply { initCause(e) } |
| 38 | + } |
15 | 39 |
|
16 | 40 | internal fun <T : Any?> Serde<T>.serializeWrappingException( |
17 | 41 | handlerContext: HandlerContext, |
|
0 commit comments