xref: /aosp_15_r20/external/kotlinx.coroutines/kotlinx-coroutines-core/common/src/selects/SelectOld.kt (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)

<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