Skip to content

Commit 4224e01

Browse files
committed
Merge termination state machine and parking state machine into one, get rid of long-scanning sequence
1 parent 4dcfced commit 4224e01

File tree

2 files changed

+18
-51
lines changed

2 files changed

+18
-51
lines changed

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

+17-50
Original file line numberDiff line numberDiff line change
@@ -303,14 +303,10 @@ internal class CoroutineScheduler(
303303
@JvmField
304304
val NOT_IN_STACK = Symbol("NOT_IN_STACK")
305305

306-
// Worker termination states
307-
private const val TERMINATION_FORBIDDEN = -1
308-
private const val TERMINATION_ALLOWED = 0
306+
// Worker ctl states
307+
private const val PARKED = -1
308+
private const val CLAIMED = 0
309309
private const val TERMINATED = 1
310-
// Worker parking states
311-
private const val PARKING_FORBIDDEN = -1
312-
private const val PARKING_ALLOWED = 0
313-
private const val PARKED = 1
314310

315311
// Masks of control state
316312
private const val BLOCKING_SHIFT = 21 // 2M threads max
@@ -443,10 +439,10 @@ internal class CoroutineScheduler(
443439
private fun tryUnpark(): Boolean {
444440
while (true) {
445441
val worker = parkedWorkersStackPop() ?: return false
446-
if (!worker.parkingState.compareAndSet(PARKING_ALLOWED, PARKING_FORBIDDEN)) {
442+
if (worker.workerCtl.compareAndSet(PARKED, CLAIMED)) {
447443
LockSupport.unpark(worker)
444+
return true
448445
}
449-
if (worker.tryForbidTermination()) return true
450446
}
451447
}
452448

@@ -596,23 +592,19 @@ internal class CoroutineScheduler(
596592
/**
597593
* Worker state. **Updated only by this worker thread**.
598594
* By default, worker is in DORMANT state in the case when it was created, but all CPU tokens or tasks were taken.
595+
* Is used locally by the worker to maintain its own invariants.
599596
*/
600597
@JvmField
601598
var state = WorkerState.DORMANT
602599

603600
/**
604-
* Small state machine for termination.
605-
* Followed states are allowed:
606-
* [TERMINATION_ALLOWED] -- worker can wake up and terminate itself
607-
* [TERMINATION_FORBIDDEN] -- worker is not allowed to terminate (because it was chosen by another thread to help)
608-
* [TERMINATED] -- final state, thread is terminating and cannot be resurrected
609-
*
610-
* Allowed transitions:
611-
* [TERMINATION_ALLOWED] -> [TERMINATION_FORBIDDEN]
612-
* [TERMINATION_ALLOWED] -> [TERMINATED]
613-
* [TERMINATION_FORBIDDEN] -> [TERMINATION_ALLOWED]
601+
* Worker control state responsible for worker claiming, parking and termination.
602+
* List of states:
603+
* [PARKED] -- worker is parked and can self-terminate after a termination deadline.
604+
* [CLAIMED] -- worker is claimed by an external submitter.
605+
* [TERMINATED] -- worker is terminated and no longer usable.
614606
*/
615-
private val terminationState = atomic(TERMINATION_ALLOWED)
607+
val workerCtl = atomic(CLAIMED)
616608

617609
/**
618610
* It is set to the termination deadline when started doing [park] and it reset
@@ -632,25 +624,8 @@ internal class CoroutineScheduler(
632624
* The delay until at least one task in other worker queues will become stealable.
633625
*/
634626
private var minDelayUntilStealableTaskNs = 0L
635-
// PARKING_ALLOWED | PARKING_FORBIDDEN | PARKED
636-
val parkingState = atomic(PARKING_ALLOWED)
637627

638628
private var rngState = Random.nextInt()
639-
/**
640-
* Tries to set [terminationState] to [TERMINATION_FORBIDDEN], returns `false` if this attempt fails.
641-
* This attempt may fail either because worker terminated itself or because someone else
642-
* claimed this worker (though this case is rare, because require very bad timings)
643-
*/
644-
fun tryForbidTermination(): Boolean =
645-
when (val state = terminationState.value) {
646-
TERMINATED -> false // already terminated
647-
TERMINATION_FORBIDDEN -> false // already forbidden, someone else claimed this worker
648-
TERMINATION_ALLOWED -> terminationState.compareAndSet(
649-
TERMINATION_ALLOWED,
650-
TERMINATION_FORBIDDEN
651-
)
652-
else -> error("Invalid terminationState = $state")
653-
}
654629

655630
/**
656631
* Tries to acquire CPU token if worker doesn't have one
@@ -730,23 +705,16 @@ internal class CoroutineScheduler(
730705
// Counterpart to "tryUnpark"
731706
private fun tryPark() {
732707
if (!inStack()) {
733-
parkingState.value = PARKING_ALLOWED
734708
parkedWorkersStackPush(this)
735709
return
736-
737710
}
738711
assert { localQueue.size == 0 }
739-
// Failed to get a parking permit => we are not in the stack
740-
while (inStack()) {
712+
workerCtl.value = PARKED // Update value once
713+
while (inStack()) { // Prevent spurious wakeups
741714
if (isTerminated || state == WorkerState.TERMINATED) break
742-
if (parkingState.value != PARKED && !parkingState.compareAndSet(PARKING_ALLOWED, PARKED)) {
743-
return
744-
}
745715
tryReleaseCpu(WorkerState.PARKING)
746716
interrupted() // Cleanup interruptions
747-
if (inStack()) {
748-
park()
749-
}
717+
park()
750718
}
751719
}
752720

@@ -798,7 +766,6 @@ internal class CoroutineScheduler(
798766
}
799767

800768
private fun park() {
801-
terminationState.value = TERMINATION_ALLOWED
802769
// set termination deadline the first time we are here (it is reset in idleReset)
803770
if (terminationDeadline == 0L) terminationDeadline = System.nanoTime() + idleWorkerKeepAliveNs
804771
// actually park
@@ -824,7 +791,7 @@ internal class CoroutineScheduler(
824791
* See tryUnpark for state reasoning.
825792
* If this CAS fails, then we were successfully unparked by other worker and cannot terminate.
826793
*/
827-
if (!terminationState.compareAndSet(TERMINATION_ALLOWED, TERMINATED)) return
794+
if (!workerCtl.compareAndSet(PARKED, TERMINATED)) return
828795
/*
829796
* At this point this thread is no longer considered as usable for scheduling.
830797
* We need multi-step choreography to reindex workers.
@@ -923,7 +890,7 @@ internal class CoroutineScheduler(
923890

924891
var currentIndex = nextInt(created)
925892
var minDelay = Long.MAX_VALUE
926-
repeat(workers.length()) {
893+
repeat(created) {
927894
++currentIndex
928895
if (currentIndex > created) currentIndex = 1
929896
val worker = workers[currentIndex]

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ internal const val NOTHING_TO_STEAL = -2L
3232
* order to properly claim value from the buffer.
3333
* Moreover, [Task] objects are reusable, so it may seem that this queue is prone to ABA problem.
3434
* Indeed it formally has ABA-problem, but the whole processing logic is written in the way that such ABA is harmless.
35-
* "I have discovered a truly marvelous proof of this, which this margin is too narrow to contain"
35+
* I have discovered a truly marvelous proof of this, which this KDoc is too narrow to contain.
3636
*/
3737
internal class WorkQueue {
3838

0 commit comments

Comments
 (0)