1 @file:OptIn(ExperimentalContracts::class)
2 
3 package kotlinx.coroutines.selects
4 
5 import kotlin.contracts.*
6 import kotlin.coroutines.*
7 
8 /**
9  * Waits for the result of multiple suspending functions simultaneously like [select], but in an _unbiased_
10  * way when multiple clauses are selectable at the same time.
11  *
12  * This unbiased implementation of `select` expression randomly shuffles the clauses before checking
13  * if they are selectable, thus ensuring that there is no statistical bias to the selection of the first
14  * clauses.
15  *
16  * See [select] function description for all the other details.
17  */
18 @OptIn(ExperimentalContracts::class)
selectUnbiasednull19 public suspend inline fun <R> selectUnbiased(crossinline builder: SelectBuilder<R>.() -> Unit): R {
20     contract {
21         callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
22     }
23     return UnbiasedSelectImplementation<R>(coroutineContext).run {
24         builder(this)
25         doSelect()
26     }
27 }
28 
29 /**
30  * The unbiased `select` inherits the [standard one][SelectImplementation],
31  * but does not register clauses immediately. Instead, it stores all of them
32  * in [clausesToRegister] lists, shuffles and registers them in the beginning of [doSelect]
33  * (see [shuffleAndRegisterClauses]), and then delegates the rest
34  * to the parent's [doSelect] implementation.
35  */
36 @PublishedApi
37 internal open class UnbiasedSelectImplementation<R>(context: CoroutineContext) : SelectImplementation<R>(context) {
38     private val clausesToRegister: MutableList<ClauseData> = arrayListOf()
39 
invokenull40     override fun SelectClause0.invoke(block: suspend () -> R) {
41         clausesToRegister += ClauseData(clauseObject, regFunc, processResFunc, PARAM_CLAUSE_0, block, onCancellationConstructor)
42     }
43 
invokenull44     override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
45         clausesToRegister += ClauseData(clauseObject, regFunc, processResFunc, null, block, onCancellationConstructor)
46     }
47 
invokenull48     override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
49         clausesToRegister += ClauseData(clauseObject, regFunc, processResFunc, param, block, onCancellationConstructor)
50     }
51 
52     @PublishedApi
doSelectnull53     override suspend fun doSelect(): R {
54         shuffleAndRegisterClauses()
55         return super.doSelect()
56     }
57 
shuffleAndRegisterClausesnull58     private fun shuffleAndRegisterClauses() = try {
59         clausesToRegister.shuffle()
60         clausesToRegister.forEach { it.register() }
61     } finally {
62         clausesToRegister.clear()
63     }
64 }
65