blob: 59bfb5d87358efe59a343619ad7fa058a4a9d3de [file] [log] [blame]
Roman Elizarov04d11ca2017-02-27 12:47:32 +03001/*
2 * Copyright 2016-2017 JetBrains s.r.o.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package kotlinx.coroutines.experimental.selects
18
Roman Elizarovaa461cf2018-04-11 13:20:29 +030019import kotlinx.coroutines.experimental.timeunit.*
20import kotlin.coroutines.experimental.*
21import kotlin.coroutines.experimental.intrinsics.*
Roman Elizarov04d11ca2017-02-27 12:47:32 +030022
23/**
24 * Waits for the result of multiple suspending functions simultaneously like [select], but in an _unbiased_
25 * way when multiple clauses are selectable at the same time.
26 *
27 * This unbiased implementation of `select` expression randomly shuffles the clauses before checking
28 * if they are selectable, thus ensuring that there is no statistical bias to the selection of the first
29 * clauses.
30 *
31 * See [select] function description for all the other details.
32 */
Roman Elizarovaa461cf2018-04-11 13:20:29 +030033public suspend inline fun <R> selectUnbiased(crossinline builder: SelectBuilder<R>.() -> Unit): R =
Roman Elizarov04d11ca2017-02-27 12:47:32 +030034 suspendCoroutineOrReturn { cont ->
35 val scope = UnbiasedSelectBuilderImpl(cont)
36 try {
37 builder(scope)
38 } catch (e: Throwable) {
39 scope.handleBuilderException(e)
40 }
41 scope.initSelectResult()
42 }
43
44
45@PublishedApi
Roman Elizarovaa461cf2018-04-11 13:20:29 +030046internal class UnbiasedSelectBuilderImpl<in R>(cont: Continuation<R>) :
47 SelectBuilder<R> {
Roman Elizarov04d11ca2017-02-27 12:47:32 +030048 val instance = SelectBuilderImpl(cont)
49 val clauses = arrayListOf<() -> Unit>()
50
51 @PublishedApi
52 internal fun handleBuilderException(e: Throwable) = instance.handleBuilderException(e)
53
54 @PublishedApi
55 internal fun initSelectResult(): Any? {
56 if (!instance.isSelected) {
57 try {
Roman Elizarovaa461cf2018-04-11 13:20:29 +030058 clauses.shuffle()
Roman Elizarov04d11ca2017-02-27 12:47:32 +030059 clauses.forEach { it.invoke() }
60 } catch (e: Throwable) {
61 instance.handleBuilderException(e)
62 }
63 }
Roman Elizarov932e8602017-06-21 17:21:37 +030064 return instance.getResult()
Roman Elizarov04d11ca2017-02-27 12:47:32 +030065 }
66
Roman Elizarovdb0e22d2017-08-29 18:15:57 +030067 override fun SelectClause0.invoke(block: suspend () -> R) {
68 clauses += { registerSelectClause0(instance, block) }
Roman Elizarov04d11ca2017-02-27 12:47:32 +030069 }
70
Roman Elizarovdb0e22d2017-08-29 18:15:57 +030071 override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
72 clauses += { registerSelectClause1(instance, block) }
Roman Elizarov04d11ca2017-02-27 12:47:32 +030073 }
74
Roman Elizarovdb0e22d2017-08-29 18:15:57 +030075 override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
76 clauses += { registerSelectClause2(instance, param, block) }
Roman Elizarov174c6962017-02-28 17:36:51 +030077 }
Roman Elizarov9d61b3e2017-04-19 18:32:11 +030078
79 override fun onTimeout(time: Long, unit: TimeUnit, block: suspend () -> R) {
80 clauses += { instance.onTimeout(time, unit, block) }
81 }
Roman Elizarov04d11ca2017-02-27 12:47:32 +030082}