blob: fa5d1c368fb93129b91c15e9f33d1751340bf9de [file] [log] [blame]
Roman Elizarov04d11ca2017-02-27 12:47:32 +03001/*
Roman Elizarov1f74a2d2018-06-29 19:19:45 +03002 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
Roman Elizarov04d11ca2017-02-27 12:47:32 +03003 */
4
5package kotlinx.coroutines.experimental.selects
6
Roman Elizarovaa461cf2018-04-11 13:20:29 +03007import kotlinx.coroutines.experimental.timeunit.*
8import kotlin.coroutines.experimental.*
9import kotlin.coroutines.experimental.intrinsics.*
Roman Elizarov04d11ca2017-02-27 12:47:32 +030010
11/**
12 * Waits for the result of multiple suspending functions simultaneously like [select], but in an _unbiased_
13 * way when multiple clauses are selectable at the same time.
14 *
15 * This unbiased implementation of `select` expression randomly shuffles the clauses before checking
16 * if they are selectable, thus ensuring that there is no statistical bias to the selection of the first
17 * clauses.
18 *
19 * See [select] function description for all the other details.
20 */
Roman Elizarovaa461cf2018-04-11 13:20:29 +030021public suspend inline fun <R> selectUnbiased(crossinline builder: SelectBuilder<R>.() -> Unit): R =
Roman Elizarov04d11ca2017-02-27 12:47:32 +030022 suspendCoroutineOrReturn { cont ->
23 val scope = UnbiasedSelectBuilderImpl(cont)
24 try {
25 builder(scope)
26 } catch (e: Throwable) {
27 scope.handleBuilderException(e)
28 }
29 scope.initSelectResult()
30 }
31
32
33@PublishedApi
Roman Elizarovaa461cf2018-04-11 13:20:29 +030034internal class UnbiasedSelectBuilderImpl<in R>(cont: Continuation<R>) :
35 SelectBuilder<R> {
Roman Elizarov04d11ca2017-02-27 12:47:32 +030036 val instance = SelectBuilderImpl(cont)
37 val clauses = arrayListOf<() -> Unit>()
38
39 @PublishedApi
40 internal fun handleBuilderException(e: Throwable) = instance.handleBuilderException(e)
41
42 @PublishedApi
43 internal fun initSelectResult(): Any? {
44 if (!instance.isSelected) {
45 try {
Roman Elizarovaa461cf2018-04-11 13:20:29 +030046 clauses.shuffle()
Roman Elizarov04d11ca2017-02-27 12:47:32 +030047 clauses.forEach { it.invoke() }
48 } catch (e: Throwable) {
49 instance.handleBuilderException(e)
50 }
51 }
Roman Elizarov932e8602017-06-21 17:21:37 +030052 return instance.getResult()
Roman Elizarov04d11ca2017-02-27 12:47:32 +030053 }
54
Roman Elizarovdb0e22d2017-08-29 18:15:57 +030055 override fun SelectClause0.invoke(block: suspend () -> R) {
56 clauses += { registerSelectClause0(instance, block) }
Roman Elizarov04d11ca2017-02-27 12:47:32 +030057 }
58
Roman Elizarovdb0e22d2017-08-29 18:15:57 +030059 override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
60 clauses += { registerSelectClause1(instance, block) }
Roman Elizarov04d11ca2017-02-27 12:47:32 +030061 }
62
Roman Elizarovdb0e22d2017-08-29 18:15:57 +030063 override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
64 clauses += { registerSelectClause2(instance, param, block) }
Roman Elizarov174c6962017-02-28 17:36:51 +030065 }
Roman Elizarov9d61b3e2017-04-19 18:32:11 +030066
67 override fun onTimeout(time: Long, unit: TimeUnit, block: suspend () -> R) {
68 clauses += { instance.onTimeout(time, unit, block) }
69 }
Roman Elizarov04d11ca2017-02-27 12:47:32 +030070}