@@ -38,55 +38,67 @@ internal fun <R, T> (suspend (R) -> T).startCoroutineUndispatched(receiver: R, c
38
38
*
39
39
* It starts the coroutine using [startCoroutineUninterceptedOrReturn].
40
40
*/
41
- internal fun <T , R > ScopeCoroutine<T>.startUndispatchedOrReturn (receiver : R , block : suspend R .() -> T ): Any? {
42
- return undispatchedResult({ true }) {
43
- block.startCoroutineUninterceptedOrReturn(receiver, this )
44
- }
45
- }
41
+ internal fun <T , R > ScopeCoroutine<T>.startUndispatchedOrReturn (
42
+ receiver : R , block : suspend R .() -> T
43
+ ): Any? = startUndspatched(alwaysRethrow = true , receiver, block)
46
44
47
45
/* *
48
46
* Same as [startUndispatchedOrReturn], but ignores [TimeoutCancellationException] on fast-path.
49
47
*/
50
48
internal fun <T , R > ScopeCoroutine<T>.startUndispatchedOrReturnIgnoreTimeout (
51
49
receiver : R , block : suspend R .() -> T
52
- ): Any? {
53
- return undispatchedResult({ e -> ! (e is TimeoutCancellationException && e.coroutine == = this ) }) {
54
- block.startCoroutineUninterceptedOrReturn(receiver, this )
55
- }
56
- }
50
+ ): Any? = startUndspatched(alwaysRethrow = false , receiver, block)
57
51
58
- private inline fun <T > ScopeCoroutine<T>.undispatchedResult (
59
- shouldThrow : (Throwable ) -> Boolean ,
60
- startBlock : () -> Any?
52
+ /* *
53
+ * Starts and handles the result of an undispatched coroutine, potentially with children.
54
+ * For example, it handles `coroutineScope { ...suspend of throw, maybe start children... }`
55
+ * and `launch(start = UNDISPATCHED) { ... }`
56
+ *
57
+ * @param alwaysRethrow specifies whether an exception should be unconditioanlly rethrown.
58
+ * It is a tweak for 'withTimeout' in order to successfully return values when the block was cancelled:
59
+ * i.e. `withTimeout(1ms) { Thread.sleep(1000); 42 }` should not fail.
60
+ */
61
+ private fun <T , R > ScopeCoroutine<T>.startUndspatched (
62
+ alwaysRethrow : Boolean ,
63
+ receiver : R , block : suspend R .() -> T
61
64
): Any? {
62
65
val result = try {
63
- startBlock()
66
+ block.startCoroutineUninterceptedOrReturn(receiver, this )
67
+ } catch (e: DispatchException ) {
68
+ // Special codepath for failing CoroutineDispatcher: rethrow an exception
69
+ // immediately without waiting for children to indicate something is wrong
70
+ dispatchExceptionAndMakeCompleting(e)
64
71
} catch (e: Throwable ) {
65
72
CompletedExceptionally (e)
66
73
}
74
+
67
75
/*
68
- * We're trying to complete our undispatched block here and have three code-paths:
69
- * (1) Coroutine is suspended.
70
- * Otherwise, coroutine had returned result, so we are completing our block (and its job).
71
- * (2) If we can't complete it or started waiting for children, we suspend.
72
- * (3) If we have successfully completed the coroutine state machine here,
73
- * then we take the actual final state of the coroutine from makeCompletingOnce and return it.
74
- *
75
- * shouldThrow parameter is a special code path for timeout coroutine:
76
- * If timeout is exceeded, but withTimeout() block was not suspended, we would like to return block value,
77
- * not a timeout exception.
76
+ * We are trying to complete our undispatched block with the following possible codepaths:
77
+ * 1) The coroutine just suspended. I.e. `coroutineScope { .. suspend here }`.
78
+ * Then just suspend
79
+ * 2) The coroutine completed with something, but has active children. Wait for them, also suspend
80
+ * 3) The coroutine succesfully completed. Return or rethrow its result.
78
81
*/
79
82
if (result == = COROUTINE_SUSPENDED ) return COROUTINE_SUSPENDED // (1)
80
83
val state = makeCompletingOnce(result)
81
84
if (state == = COMPLETING_WAITING_CHILDREN ) return COROUTINE_SUSPENDED // (2)
82
85
afterCompletionUndispatched()
83
86
return if (state is CompletedExceptionally ) { // (3)
84
87
when {
85
- shouldThrow (state.cause) -> throw recoverStackTrace(state.cause, uCont)
88
+ alwaysRethrow || notOwnTimeout (state.cause) -> throw recoverStackTrace(state.cause, uCont)
86
89
result is CompletedExceptionally -> throw recoverStackTrace(result.cause, uCont)
87
90
else -> result
88
91
}
89
92
} else {
90
93
state.unboxState()
91
94
}
92
95
}
96
+
97
+ private fun ScopeCoroutine <* >.notOwnTimeout (cause : Throwable ): Boolean {
98
+ return cause !is TimeoutCancellationException || cause.coroutine != = this
99
+ }
100
+
101
+ private fun ScopeCoroutine <* >.dispatchExceptionAndMakeCompleting (e : DispatchException ): Nothing {
102
+ makeCompleting(CompletedExceptionally (e.cause))
103
+ throw recoverStackTrace(e.cause, uCont)
104
+ }
0 commit comments