16
16
package com.uber.rib.core
17
17
18
18
import com.uber.autodispose.coroutinesinterop.asScopeProvider
19
- import kotlin.contracts.ExperimentalContracts
20
- import kotlin.contracts.InvocationKind
21
- import kotlin.contracts.contract
22
- import kotlin.coroutines.ContinuationInterceptor
23
19
import kotlin.coroutines.CoroutineContext
24
20
import kotlin.coroutines.EmptyCoroutineContext
25
- import kotlin.coroutines.coroutineContext
26
- import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
27
- import kotlin.coroutines.intrinsics.intercepted
28
- import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
29
- import kotlin.coroutines.resume
30
21
import kotlinx.coroutines.CancellationException
31
22
import kotlinx.coroutines.CompletableJob
32
- import kotlinx.coroutines.CoroutineDispatcher
33
23
import kotlinx.coroutines.CoroutineScope
34
24
import kotlinx.coroutines.CoroutineStart
35
25
import kotlinx.coroutines.Job
@@ -122,10 +112,17 @@ public fun CoroutineScope.bind(
122
112
worker : RibCoroutineWorker ,
123
113
context : CoroutineContext = RibDispatchers .Default ,
124
114
): BindWorkerHandle {
125
- val bindJob: CompletableJob // A job that completes once worker's onStart completes
115
+ var bindJob: CompletableJob ? = null // A job that completes once worker's onStart completes
126
116
val unbindJob =
127
- launch(context, { bindJob = createBindingJob() }) { bindAndAwaitCancellation(worker, bindJob) }
128
- return BindWorkerHandleImpl (bindJob, unbindJob)
117
+ launch(context, CoroutineStart .UNDISPATCHED ) {
118
+ val job = createBindingJob()
119
+ bindJob = job
120
+ // launch again -- this time, we will dispatch if installed dispatcher
121
+ // tell us to (CoroutineDispatcher.isDispatchNeeded()).
122
+ launch { bindAndAwaitCancellation(worker, job) }
123
+ }
124
+ // !! is safe here -- outer coroutine was started undispatched.
125
+ return BindWorkerHandleImpl (bindJob!! , unbindJob)
129
126
}
130
127
131
128
/* * Binds [workers] in a scope that is a child of the [CoroutineScope] receiver. */
@@ -139,46 +136,6 @@ public fun CoroutineScope.bind(
139
136
}
140
137
}
141
138
142
- /* *
143
- * Guarantees to run synchronous [init] block exactly once in an undispatched manner.
144
- *
145
- * **Exceptions thrown in [init] block will be rethrown at call site.**
146
- */
147
- @OptIn(ExperimentalContracts ::class )
148
- private fun CoroutineScope.launch (
149
- context : CoroutineContext = EmptyCoroutineContext ,
150
- init : CoroutineScope .() -> Unit = {},
151
- block : suspend CoroutineScope .() -> Unit ,
152
- ): Job {
153
- contract {
154
- callsInPlace(init , InvocationKind .EXACTLY_ONCE )
155
- callsInPlace(block, InvocationKind .AT_MOST_ONCE )
156
- }
157
- var initError: Throwable ? = null
158
- val job =
159
- launch(context, CoroutineStart .UNDISPATCHED ) {
160
- runCatching(init ).onFailure { initError = it }.getOrThrow()
161
- dispatchIfNeeded()
162
- block()
163
- }
164
- initError?.let { throw it }
165
- return job
166
- }
167
-
168
- private suspend inline fun dispatchIfNeeded () {
169
- suspendCoroutineUninterceptedOrReturn sc@{ cont ->
170
- val context = cont.context
171
- val dispatcher = context[ContinuationInterceptor ] as CoroutineDispatcher
172
- if (! dispatcher.isDispatchNeeded(context)) return @sc Unit
173
- // Coroutine was not in the right context -- we'll dispatch.
174
- context.ensureActive()
175
- cont.intercepted().resume(Unit )
176
- COROUTINE_SUSPENDED
177
- }
178
- // Don't continue if coroutine was cancelled after returning from dispatch.
179
- coroutineContext.ensureActive()
180
- }
181
-
182
139
private fun CoroutineScope.createBindingJob (): CompletableJob =
183
140
Job (coroutineContext.job).also {
184
141
// Cancel `unbindJob` if `bindJob` has cancelled. This is important to abort `onStart` if
0 commit comments