|
4 | 4 |
|
5 | 5 | @file:JvmMultifileClass |
6 | 6 | @file:JvmName("BuildersKt") |
| 7 | +@file:OptIn(ExperimentalContracts::class) |
7 | 8 |
|
8 | 9 | package kotlinx.coroutines |
9 | 10 |
|
10 | 11 | import kotlinx.atomicfu.* |
11 | 12 | import kotlinx.coroutines.internal.* |
12 | 13 | import kotlinx.coroutines.intrinsics.* |
13 | 14 | import kotlinx.coroutines.selects.* |
| 15 | +import kotlin.contracts.* |
14 | 16 | import kotlin.coroutines.* |
15 | 17 | import kotlin.coroutines.intrinsics.* |
16 | 18 | import kotlin.jvm.* |
@@ -134,31 +136,36 @@ private class LazyDeferredCoroutine<T>( |
134 | 136 | public suspend fun <T> withContext( |
135 | 137 | context: CoroutineContext, |
136 | 138 | block: suspend CoroutineScope.() -> T |
137 | | -): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont -> |
138 | | - // compute new context |
139 | | - val oldContext = uCont.context |
140 | | - val newContext = oldContext + context |
141 | | - // always check for cancellation of new context |
142 | | - newContext.checkCompletion() |
143 | | - // FAST PATH #1 -- new context is the same as the old one |
144 | | - if (newContext === oldContext) { |
145 | | - val coroutine = ScopeCoroutine(newContext, uCont) |
146 | | - return@sc coroutine.startUndispatchedOrReturn(coroutine, block) |
| 139 | +): T { |
| 140 | + contract { |
| 141 | + callsInPlace(block, InvocationKind.EXACTLY_ONCE) |
147 | 142 | } |
148 | | - // FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed) |
149 | | - // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher) |
150 | | - if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) { |
151 | | - val coroutine = UndispatchedCoroutine(newContext, uCont) |
152 | | - // There are changes in the context, so this thread needs to be updated |
153 | | - withCoroutineContext(newContext, null) { |
| 143 | + return suspendCoroutineUninterceptedOrReturn sc@ { uCont -> |
| 144 | + // compute new context |
| 145 | + val oldContext = uCont.context |
| 146 | + val newContext = oldContext + context |
| 147 | + // always check for cancellation of new context |
| 148 | + newContext.checkCompletion() |
| 149 | + // FAST PATH #1 -- new context is the same as the old one |
| 150 | + if (newContext === oldContext) { |
| 151 | + val coroutine = ScopeCoroutine(newContext, uCont) |
154 | 152 | return@sc coroutine.startUndispatchedOrReturn(coroutine, block) |
155 | 153 | } |
| 154 | + // FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed) |
| 155 | + // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher) |
| 156 | + if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) { |
| 157 | + val coroutine = UndispatchedCoroutine(newContext, uCont) |
| 158 | + // There are changes in the context, so this thread needs to be updated |
| 159 | + withCoroutineContext(newContext, null) { |
| 160 | + return@sc coroutine.startUndispatchedOrReturn(coroutine, block) |
| 161 | + } |
| 162 | + } |
| 163 | + // SLOW PATH -- use new dispatcher |
| 164 | + val coroutine = DispatchedCoroutine(newContext, uCont) |
| 165 | + coroutine.initParentJob() |
| 166 | + block.startCoroutineCancellable(coroutine, coroutine) |
| 167 | + coroutine.getResult() |
156 | 168 | } |
157 | | - // SLOW PATH -- use new dispatcher |
158 | | - val coroutine = DispatchedCoroutine(newContext, uCont) |
159 | | - coroutine.initParentJob() |
160 | | - block.startCoroutineCancellable(coroutine, coroutine) |
161 | | - coroutine.getResult() |
162 | 169 | } |
163 | 170 |
|
164 | 171 | /** |
|
0 commit comments