Implement yield for unconfined dispatchers, documentation improvements
Fixes #737
diff --git a/common/kotlinx-coroutines-core-common/src/Dispatched.kt b/common/kotlinx-coroutines-core-common/src/Dispatched.kt
index 32ee51f..e8bd658 100644
--- a/common/kotlinx-coroutines-core-common/src/Dispatched.kt
+++ b/common/kotlinx-coroutines-core-common/src/Dispatched.kt
@@ -21,26 +21,33 @@
@JvmField
internal val threadLocalEventLoop = CommonThreadLocal { EventLoop() }
- inline fun execute(continuation: DispatchedContinuation<*>, contState: Any?, mode: Int, block: () -> Unit) {
+ inline fun execute(continuation: DispatchedContinuation<*>, contState: Any?, mode: Int, doYield: Boolean = false, block: () -> Unit) : Boolean {
val eventLoop = threadLocalEventLoop.get()
if (eventLoop.isActive) {
+ // If we are yielding and queue is empty, yield should be a no-op
+ if (doYield && eventLoop.queue.isEmpty) {
+ return false
+ }
+
continuation._state = contState
continuation.resumeMode = mode
eventLoop.queue.addLast(continuation)
- return
+ return true
}
runEventLoop(eventLoop, block)
+ return false
}
- fun resumeUndispatched(task: DispatchedTask<*>) {
+ fun resumeUndispatched(task: DispatchedTask<*>): Boolean {
val eventLoop = threadLocalEventLoop.get()
if (eventLoop.isActive) {
eventLoop.queue.addLast(task)
- return
+ return true
}
runEventLoop(eventLoop, { task.resume(task.delegate, MODE_UNDISPATCHED) })
+ return false
}
inline fun runEventLoop(eventLoop: EventLoop, block: () -> Unit) {
@@ -227,6 +234,12 @@
}
}
+internal fun DispatchedContinuation<Unit>.yield(): Boolean {
+ return UndispatchedEventLoop.execute(this, Unit, MODE_CANCELLABLE, true) {
+ run()
+ }
+}
+
internal fun <T> DispatchedTask<T>.dispatch(mode: Int = MODE_CANCELLABLE) {
val delegate = this.delegate
if (mode.isDispatchedMode && delegate is DispatchedContinuation<*> && mode.isCancellableMode == resumeMode.isCancellableMode) {
diff --git a/common/kotlinx-coroutines-core-common/src/Supervisor.kt b/common/kotlinx-coroutines-core-common/src/Supervisor.kt
index c222f66..f150737 100644
--- a/common/kotlinx-coroutines-core-common/src/Supervisor.kt
+++ b/common/kotlinx-coroutines-core-common/src/Supervisor.kt
@@ -23,7 +23,7 @@
*
* If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
* parent fails or is cancelled. All this supervisor's children are cancelled in this case, too. The invocation of
- * of [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
+ * [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
*
* @param parent an optional parent job.
*/
diff --git a/common/kotlinx-coroutines-core-common/src/Yield.kt b/common/kotlinx-coroutines-core-common/src/Yield.kt
index 632dcba..fde3391 100644
--- a/common/kotlinx-coroutines-core-common/src/Yield.kt
+++ b/common/kotlinx-coroutines-core-common/src/Yield.kt
@@ -19,7 +19,9 @@
val context = uCont.context
context.checkCompletion()
val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
- if (!cont.dispatcher.isDispatchNeeded(context)) return@sc Unit
+ if (!cont.dispatcher.isDispatchNeeded(context)) {
+ return@sc if (cont.yield()) COROUTINE_SUSPENDED else Unit
+ }
cont.dispatchYield(Unit)
COROUTINE_SUSPENDED
}
diff --git a/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt b/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt
index a6bf8f6..5cfb8e8 100644
--- a/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt
+++ b/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt
@@ -8,6 +8,7 @@
private var elements = arrayOfNulls<Any>(16)
private var head = 0
private var tail = 0
+ val isEmpty: Boolean get() = head == tail
public fun addLast(element: T) {
elements[tail] = element
diff --git a/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt b/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt
index 8866057..f37c356 100644
--- a/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt
@@ -54,7 +54,7 @@
}
@Test
- fun enterMultipleTimes() = runTest {
+ fun testEnterMultipleTimes() = runTest {
launch(Unconfined) {
expect(1)
}
@@ -70,5 +70,46 @@
finish(4)
}
+ @Test
+ fun testYield() = runTest {
+ expect(1)
+ launch(Dispatchers.Unconfined) {
+ expect(2)
+ yield()
+ launch {
+ expect(4)
+ }
+ expect(3)
+ yield()
+ expect(5)
+ }.join()
+
+ finish(6)
+ }
+
+ @Test
+ fun testCancellationWihYields() = runTest {
+ expect(1)
+ GlobalScope.launch(Dispatchers.Unconfined) {
+ val job = coroutineContext[Job]!!
+ expect(2)
+ yield()
+ GlobalScope.launch(Dispatchers.Unconfined) {
+ expect(4)
+ job.cancel()
+ expect(5)
+ }
+ expect(3)
+
+ try {
+ yield()
+ } finally {
+ expect(6)
+ }
+ }
+
+ finish(7)
+ }
+
class TestException : Throwable()
}
diff --git a/docs/shared-mutable-state-and-concurrency.md b/docs/shared-mutable-state-and-concurrency.md
index 4a44d3c..7530861 100644
--- a/docs/shared-mutable-state-and-concurrency.md
+++ b/docs/shared-mutable-state-and-concurrency.md
@@ -42,7 +42,7 @@
Let us launch a hundred coroutines all doing the same action thousand times.
We'll also measure their completion time for further comparisons:
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
+<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
suspend fun CoroutineScope.massiveRun(action: suspend () -> Unit) {