All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.selects.SelectOld.kt Maven / Gradle / Ivy

package kotlinx.coroutines.selects

import kotlinx.coroutines.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*

/*
 * For binary compatibility, we need to maintain the previous `select` implementations.
 * Thus, we keep [SelectBuilderImpl] and [UnbiasedSelectBuilderImpl] and implement the
 * functions marked with `@PublishedApi`.
 *
 * We keep the old `select` functions as [selectOld] and [selectUnbiasedOld] for test purpose.
 */

@PublishedApi
internal class SelectBuilderImpl(
    uCont: Continuation // unintercepted delegate continuation
) : SelectImplementation(uCont.context) {
    private val cont = CancellableContinuationImpl(uCont.intercepted(), MODE_CANCELLABLE)

    @PublishedApi
    internal fun getResult(): Any? {
        // In the current `select` design, the [select] and [selectUnbiased] functions
        // do not wrap the operation in `suspendCoroutineUninterceptedOrReturn` and
        // suspend explicitly via [doSelect] call, which returns the final result.
        // However, [doSelect] is a suspend function, so it cannot be invoked directly.
        // In addition, the `select` builder is eligible to throw an exception, which
        // should be handled properly.
        //
        // As a solution, we:
        // 1) check whether the `select` building is already completed with exception, finishing immediately in this case;
        // 2) create a CancellableContinuationImpl with the provided unintercepted continuation as a delegate;
        // 3) wrap the [doSelect] call in an additional coroutine, which we launch in UNDISPATCHED mode;
        // 4) resume the created CancellableContinuationImpl after the [doSelect] invocation completes;
        // 5) use CancellableContinuationImpl.getResult() as a result of this function.
        if (cont.isCompleted) return cont.getResult()
        CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) {
            val result = try {
                doSelect()
            } catch (e: Throwable) {
                cont.resumeUndispatchedWithException(e)
                return@launch
            }
            cont.resumeUndispatched(result)
        }
        return cont.getResult()
    }

    @PublishedApi
    internal fun handleBuilderException(e: Throwable) {
        cont.resumeWithException(e) // will be thrown later via `cont.getResult()`
    }
}

@PublishedApi
internal class UnbiasedSelectBuilderImpl(
    uCont: Continuation // unintercepted delegate continuation
) : UnbiasedSelectImplementation(uCont.context) {
    private val cont = CancellableContinuationImpl(uCont.intercepted(), MODE_CANCELLABLE)

    @PublishedApi
    internal fun initSelectResult(): Any? {
        // Here, we do the same trick as in [SelectBuilderImpl].
        if (cont.isCompleted) return cont.getResult()
        CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) {
            val result = try {
                doSelect()
            } catch (e: Throwable) {
                cont.resumeUndispatchedWithException(e)
                return@launch
            }
            cont.resumeUndispatched(result)
        }
        return cont.getResult()
    }

    @PublishedApi
    internal fun handleBuilderException(e: Throwable) {
        cont.resumeWithException(e)
    }
}

/*
 * This is the old version of `select`. It should work to guarantee binary compatibility.
 *
 * Internal note:
 * We do test it manually by changing the implementation of **new** select with the following:
 * ```
 * public suspend inline fun  select(crossinline builder: SelectBuilder.() -> Unit): R {
 *     contract {
 *         callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
 *     }
 *     return selectOld(builder)
 * }
 * ```
 *
 * These signatures are not used by the already compiled code, but their body is.
 */
@PublishedApi
internal suspend inline fun  selectOld(crossinline builder: SelectBuilder.() -> Unit): R {
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val scope = SelectBuilderImpl(uCont)
        try {
            builder(scope)
        } catch (e: Throwable) {
            scope.handleBuilderException(e)
        }
        scope.getResult()
    }
}

// This is the old version of `selectUnbiased`. It should work to guarantee binary compatibility.
@PublishedApi
internal suspend inline fun  selectUnbiasedOld(crossinline builder: SelectBuilder.() -> Unit): R =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val scope = UnbiasedSelectBuilderImpl(uCont)
        try {
            builder(scope)
        } catch (e: Throwable) {
            scope.handleBuilderException(e)
        }
        scope.initSelectResult()
    }

@OptIn(ExperimentalStdlibApi::class)
private fun  CancellableContinuation.resumeUndispatched(result: T) {
    val dispatcher = context[CoroutineDispatcher]
    if (dispatcher != null) {
        dispatcher.resumeUndispatched(result)
    } else {
        resume(result)
    }
}

@OptIn(ExperimentalStdlibApi::class)
private fun CancellableContinuation<*>.resumeUndispatchedWithException(exception: Throwable) {
    val dispatcher = context[CoroutineDispatcher]
    if (dispatcher != null) {
        dispatcher.resumeUndispatchedWithException(exception)
    } else {
        resumeWithException(exception)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy