<lambda>null1 package kotlinx.coroutines.selects
2
3 import kotlinx.coroutines.*
4 import kotlin.coroutines.*
5 import kotlin.coroutines.intrinsics.*
6
7 /*
8 * For binary compatibility, we need to maintain the previous `select` implementations.
9 * Thus, we keep [SelectBuilderImpl] and [UnbiasedSelectBuilderImpl] and implement the
10 * functions marked with `@PublishedApi`.
11 *
12 * We keep the old `select` functions as [selectOld] and [selectUnbiasedOld] for test purpose.
13 */
14
15 @PublishedApi
16 internal class SelectBuilderImpl<R>(
17 uCont: Continuation<R> // unintercepted delegate continuation
18 ) : SelectImplementation<R>(uCont.context) {
19 private val cont = CancellableContinuationImpl(uCont.intercepted(), MODE_CANCELLABLE)
20
21 @PublishedApi
22 internal fun getResult(): Any? {
23 // In the current `select` design, the [select] and [selectUnbiased] functions
24 // do not wrap the operation in `suspendCoroutineUninterceptedOrReturn` and
25 // suspend explicitly via [doSelect] call, which returns the final result.
26 // However, [doSelect] is a suspend function, so it cannot be invoked directly.
27 // In addition, the `select` builder is eligible to throw an exception, which
28 // should be handled properly.
29 //
30 // As a solution, we:
31 // 1) check whether the `select` building is already completed with exception, finishing immediately in this case;
32 // 2) create a CancellableContinuationImpl with the provided unintercepted continuation as a delegate;
33 // 3) wrap the [doSelect] call in an additional coroutine, which we launch in UNDISPATCHED mode;
34 // 4) resume the created CancellableContinuationImpl after the [doSelect] invocation completes;
35 // 5) use CancellableContinuationImpl.getResult() as a result of this function.
36 if (cont.isCompleted) return cont.getResult()
37 CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) {
38 val result = try {
39 doSelect()
40 } catch (e: Throwable) {
41 cont.resumeUndispatchedWithException(e)
42 return@launch
43 }
44 cont.resumeUndispatched(result)
45 }
46 return cont.getResult()
47 }
48
49 @PublishedApi
50 internal fun handleBuilderException(e: Throwable) {
51 cont.resumeWithException(e) // will be thrown later via `cont.getResult()`
52 }
53 }
54
55 @PublishedApi
56 internal class UnbiasedSelectBuilderImpl<R>(
57 uCont: Continuation<R> // unintercepted delegate continuation
58 ) : UnbiasedSelectImplementation<R>(uCont.context) {
59 private val cont = CancellableContinuationImpl(uCont.intercepted(), MODE_CANCELLABLE)
60
61 @PublishedApi
initSelectResultnull62 internal fun initSelectResult(): Any? {
63 // Here, we do the same trick as in [SelectBuilderImpl].
64 if (cont.isCompleted) return cont.getResult()
65 CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) {
66 val result = try {
67 doSelect()
68 } catch (e: Throwable) {
69 cont.resumeUndispatchedWithException(e)
70 return@launch
71 }
72 cont.resumeUndispatched(result)
73 }
74 return cont.getResult()
75 }
76
77 @PublishedApi
handleBuilderExceptionnull78 internal fun handleBuilderException(e: Throwable) {
79 cont.resumeWithException(e)
80 }
81 }
82
83 /*
84 * This is the old version of `select`. It should work to guarantee binary compatibility.
85 *
86 * Internal note:
87 * We do test it manually by changing the implementation of **new** select with the following:
88 * ```
89 * public suspend inline fun <R> select(crossinline builder: SelectBuilder<R>.() -> Unit): R {
90 * contract {
91 * callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
92 * }
93 * return selectOld(builder)
94 * }
95 * ```
96 *
97 * These signatures are not used by the already compiled code, but their body is.
98 */
99 @PublishedApi
selectOldnull100 internal suspend inline fun <R> selectOld(crossinline builder: SelectBuilder<R>.() -> Unit): R {
101 return suspendCoroutineUninterceptedOrReturn { uCont ->
102 val scope = SelectBuilderImpl(uCont)
103 try {
104 builder(scope)
105 } catch (e: Throwable) {
106 scope.handleBuilderException(e)
107 }
108 scope.getResult()
109 }
110 }
111
112 // This is the old version of `selectUnbiased`. It should work to guarantee binary compatibility.
113 @PublishedApi
selectUnbiasedOldnull114 internal suspend inline fun <R> selectUnbiasedOld(crossinline builder: SelectBuilder<R>.() -> Unit): R =
115 suspendCoroutineUninterceptedOrReturn { uCont ->
116 val scope = UnbiasedSelectBuilderImpl(uCont)
117 try {
118 builder(scope)
119 } catch (e: Throwable) {
120 scope.handleBuilderException(e)
121 }
122 scope.initSelectResult()
123 }
124
125 @OptIn(ExperimentalStdlibApi::class)
resumeUndispatchednull126 private fun <T> CancellableContinuation<T>.resumeUndispatched(result: T) {
127 val dispatcher = context[CoroutineDispatcher]
128 if (dispatcher != null) {
129 dispatcher.resumeUndispatched(result)
130 } else {
131 resume(result)
132 }
133 }
134
135 @OptIn(ExperimentalStdlibApi::class)
CancellableContinuationnull136 private fun CancellableContinuation<*>.resumeUndispatchedWithException(exception: Throwable) {
137 val dispatcher = context[CoroutineDispatcher]
138 if (dispatcher != null) {
139 dispatcher.resumeUndispatchedWithException(exception)
140 } else {
141 resumeWithException(exception)
142 }
143 }
144