Implemented "selectUnbiased"
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
index 14ad07e..7380ef7 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
@@ -118,6 +118,10 @@
* becomes the result of the select. If any clause _fails_, then the select invocation produces the
* corresponding exception. No clause is selected in this case.
*
+ * This select function is _biased_ to the first clause. When multiple clauses can be selected at the same time,
+ * the first one of them gets priority. Use [selectUnbiased] for an unbiased (randomized) selection among
+ * the clauses.
+
* There is no `default` clause for select expression. Instead, each selectable suspending function has the
* corresponding non-suspending version that can be used with a regular `when` expression to select one
* of the alternatives or to perform default (`else`) action if none of them can be immediately selected.
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt
new file mode 100644
index 0000000..a0ff5e2
--- /dev/null
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/selects/SelectUnbiased.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental.selects
+
+import kotlinx.coroutines.experimental.Deferred
+import kotlinx.coroutines.experimental.Job
+import kotlinx.coroutines.experimental.channels.ReceiveChannel
+import kotlinx.coroutines.experimental.channels.SendChannel
+import java.util.*
+import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
+
+/**
+ * Waits for the result of multiple suspending functions simultaneously like [select], but in an _unbiased_
+ * way when multiple clauses are selectable at the same time.
+ *
+ * This unbiased implementation of `select` expression randomly shuffles the clauses before checking
+ * if they are selectable, thus ensuring that there is no statistical bias to the selection of the first
+ * clauses.
+ *
+ * See [select] function description for all the other details.
+ */
+public inline suspend fun <R> selectUnbiased(crossinline builder: SelectBuilder<R>.() -> Unit): R =
+ suspendCoroutineOrReturn { cont ->
+ val scope = UnbiasedSelectBuilderImpl(cont)
+ try {
+ builder(scope)
+ } catch (e: Throwable) {
+ scope.handleBuilderException(e)
+ }
+ scope.initSelectResult()
+ }
+
+
+@PublishedApi
+internal class UnbiasedSelectBuilderImpl<in R>(cont: Continuation<R>) : SelectBuilder<R> {
+ val instance = SelectBuilderImpl(cont)
+ val clauses = arrayListOf<() -> Unit>()
+
+ @PublishedApi
+ internal fun handleBuilderException(e: Throwable) = instance.handleBuilderException(e)
+
+ @PublishedApi
+ internal fun initSelectResult(): Any? {
+ if (!instance.isSelected) {
+ try {
+ Collections.shuffle(clauses)
+ clauses.forEach { it.invoke() }
+ } catch (e: Throwable) {
+ instance.handleBuilderException(e)
+ }
+ }
+ return instance.initSelectResult()
+ }
+
+ override fun Job.onJoin(block: suspend () -> R) {
+ clauses += { registerSelectJoin(instance, block) }
+ }
+
+ override fun <T> Deferred<T>.onAwait(block: suspend (T) -> R) {
+ clauses += { registerSelectAwait(instance, block) }
+ }
+
+ override fun <E> SendChannel<E>.onSend(element: E, block: suspend () -> R) {
+ clauses += { registerSelectSend(instance, element, block) }
+ }
+
+ override fun <E> ReceiveChannel<E>.onReceive(block: suspend (E) -> R) {
+ clauses += { registerSelectReceive(instance, block) }
+ }
+
+ override fun <E> ReceiveChannel<E>.onReceiveOrNull(block: suspend (E?) -> R) {
+ clauses += { registerSelectReceiveOrNull(instance, block) }
+ }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt b/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt
new file mode 100644
index 0000000..49e74e4
--- /dev/null
+++ b/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectBiasTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.coroutines.experimental.selects
+
+import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.experimental.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class SelectBiasTest {
+ val n = 10_000
+
+ @Test
+ fun testBiased() = runBlocking<Unit> {
+ val d0 = async(context) { 0 }
+ val d1 = async(context) { 1 }
+ val counter = IntArray(2)
+ repeat(n) {
+ val selected = select<Int> {
+ d0.onAwait { 0 }
+ d1.onAwait { 1 }
+ }
+ counter[selected]++
+ }
+ assertEquals(n, counter[0])
+ assertEquals(0, counter[1])
+ }
+
+ @Test
+ fun testUnbiased() = runBlocking<Unit> {
+ val d0 = async(context) { 0 }
+ val d1 = async(context) { 1 }
+ val counter = IntArray(2)
+ repeat(n) {
+ val selected = selectUnbiased<Int> {
+ d0.onAwait { 0 }
+ d1.onAwait { 1 }
+ }
+ counter[selected]++
+ }
+ assertTrue(counter[0] >= n / 4)
+ assertTrue(counter[1] >= n / 4)
+ }
+}
\ No newline at end of file