Skip to content

Commit c91192e

Browse files
committed
Revert "Document and fix dispatchYield codepath (Kotlin#4255)"
This reverted introduces regression IJPL-182130. We did not get this deadlock before, so it seems safe to revert the commit This reverts commit d30af7c.
1 parent 7de6aad commit c91192e

File tree

4 files changed

+20
-29
lines changed

4 files changed

+20
-29
lines changed

kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,6 @@ public abstract class CoroutineDispatcher :
222222
* Though the `yield` marker may be passed as a part of [context], this
223223
* is a separate method for performance reasons.
224224
*
225-
* Implementation note: this entry-point is used for `Dispatchers.IO` and [Dispatchers.Default]
226-
* unerlying implementations, see overrides for this method.
227-
*
228225
* @suppress **This an internal API and should not be used from general code.**
229226
*/
230227
@InternalCoroutinesApi

kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,14 @@ internal class CoroutineScheduler(
410410
* this [block] may execute blocking operations (IO, system calls, locking primitives etc.)
411411
*
412412
* [taskContext] -- concurrency context of given [block].
413-
* [fair] -- whether this [dispatch] call is fair.
414-
* If `true` then the task will be dispatched in a FIFO manner.
413+
* [tailDispatch] -- whether this [dispatch] call is the last action the (presumably) worker thread does in its current task.
414+
* If `true`, then the task will be dispatched in a FIFO manner and no additional workers will be requested,
415+
* but only if the current thread is a corresponding worker thread.
415416
* Note that caller cannot be ensured that it is being executed on worker thread for the following reasons:
416417
* - [CoroutineStart.UNDISPATCHED]
417-
* - Concurrent [close] that effectively shutdowns the worker thread.
418-
* Used for [yield].
418+
* - Concurrent [close] that effectively shutdowns the worker thread
419419
*/
420-
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
420+
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
421421
trackTask() // this is needed for virtual time support
422422
val task = createTask(block, taskContext)
423423
val isBlockingTask = task.isBlocking
@@ -426,18 +426,20 @@ internal class CoroutineScheduler(
426426
val stateSnapshot = if (isBlockingTask) incrementBlockingTasks() else 0
427427
// try to submit the task to the local queue and act depending on the result
428428
val currentWorker = currentWorker()
429-
val notAdded = currentWorker.submitToLocalQueue(task, fair)
429+
val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
430430
if (notAdded != null) {
431431
if (!addToGlobalQueue(notAdded)) {
432432
// Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted
433433
throw RejectedExecutionException("$schedulerName was terminated")
434434
}
435435
}
436+
val skipUnpark = tailDispatch && currentWorker != null
436437
// Checking 'task' instead of 'notAdded' is completely okay
437438
if (isBlockingTask) {
438439
// Use state snapshot to better estimate the number of running threads
439-
signalBlockingWork(stateSnapshot)
440+
signalBlockingWork(stateSnapshot, skipUnpark = skipUnpark)
440441
} else {
442+
if (skipUnpark) return
441443
signalCpuWork()
442444
}
443445
}
@@ -453,7 +455,8 @@ internal class CoroutineScheduler(
453455
}
454456

455457
// NB: should only be called from 'dispatch' method due to blocking tasks increment
456-
private fun signalBlockingWork(stateSnapshot: Long) {
458+
private fun signalBlockingWork(stateSnapshot: Long, skipUnpark: Boolean) {
459+
if (skipUnpark) return
457460
if (tryUnpark()) return
458461
// Use state snapshot to avoid accidental thread overprovision
459462
if (tryCreateWorker(stateSnapshot)) return
@@ -526,7 +529,7 @@ internal class CoroutineScheduler(
526529
* Returns `null` if task was successfully added or an instance of the
527530
* task that was not added or replaced (thus should be added to global queue).
528531
*/
529-
private fun Worker?.submitToLocalQueue(task: Task, fair: Boolean): Task? {
532+
private fun Worker?.submitToLocalQueue(task: Task, tailDispatch: Boolean): Task? {
530533
if (this == null) return task
531534
/*
532535
* This worker could have been already terminated from this thread by close/shutdown and it should not
@@ -538,7 +541,7 @@ internal class CoroutineScheduler(
538541
return task
539542
}
540543
mayHaveLocalTasks = true
541-
return localQueue.add(task, fair = fair)
544+
return localQueue.add(task, fair = tailDispatch)
542545
}
543546

544547
private fun currentWorker(): Worker? = (Thread.currentThread() as? Worker)?.takeIf { it.scheduler == this }

kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,21 +124,11 @@ internal open class SchedulerCoroutineDispatcher(
124124

125125
override fun dispatch(context: CoroutineContext, block: Runnable): Unit = coroutineScheduler.dispatch(block)
126126

127-
override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit {
128-
/*
129-
* 'dispatchYield' implementation is needed to address the scheduler's scheduling policy.
130-
* By default, the scheduler dispatches tasks in a semi-LIFO order, meaning that for the
131-
* task sequence [#1, #2, #3], the scheduling of task #4 will produce
132-
* [#4, #1, #2, #3], allocates new worker and makes #4 stealable after some time.
133-
* On a fast enough system, it means that `while (true) { yield() }` might obstruct the progress
134-
* of the system and potentially starve it.
135-
* To mitigate that, `dispatchYield` is a dedicated entry point that produces [#1, #2, #3, #4]
136-
*/
137-
coroutineScheduler.dispatch(block, fair = true)
138-
}
139-
140-
internal fun dispatchWithContext(block: Runnable, context: TaskContext, fair: Boolean) {
141-
coroutineScheduler.dispatch(block, context, fair)
127+
override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
128+
coroutineScheduler.dispatch(block, tailDispatch = true)
129+
130+
internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
131+
coroutineScheduler.dispatch(block, context, tailDispatch)
142132
}
143133

144134
override fun close() {

kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package kotlinx.coroutines.scheduling
22

33
import kotlinx.coroutines.testing.*
4+
import kotlinx.coroutines.*
45
import org.junit.Test
56
import java.lang.Runnable
67
import java.util.concurrent.*
@@ -79,7 +80,7 @@ class CoroutineSchedulerTest : TestBase() {
7980
it.dispatch(Runnable {
8081
expect(2)
8182
finishLatch.countDown()
82-
}, fair = true)
83+
}, tailDispatch = true)
8384
})
8485

8586
startLatch.countDown()

0 commit comments

Comments
 (0)