@@ -16,6 +16,14 @@ internal const val MASK = BUFFER_CAPACITY - 1 // 128 by default
16
16
internal const val TASK_STOLEN = - 1L
17
17
internal const val NOTHING_TO_STEAL = - 2L
18
18
19
+ internal typealias StealingMode = Int
20
+ internal const val STEAL_ANY : StealingMode = 3
21
+ internal const val STEAL_CPU_ONLY : StealingMode = 2
22
+ internal const val STEAL_BLOCKING_ONLY : StealingMode = 1
23
+
24
+ internal inline val Task .maskForStealingMode: Int
25
+ get() = if (isBlocking) STEAL_BLOCKING_ONLY else STEAL_CPU_ONLY
26
+
19
27
/* *
20
28
* Tightly coupled with [CoroutineScheduler] queue of pending tasks, but extracted to separate file for simplicity.
21
29
* At any moment queue is used only by [CoroutineScheduler.Worker] threads, has only one producer (worker owning this queue)
@@ -107,54 +115,75 @@ internal class WorkQueue {
107
115
*
108
116
* Returns [NOTHING_TO_STEAL] if queue has nothing to steal, [TASK_STOLEN] if at least task was stolen
109
117
* or positive value of how many nanoseconds should pass until the head of this queue will be available to steal.
118
+ *
119
+ * [StealingMode] controls what tasks to steal:
120
+ * * [STEAL_ANY] is default mode for scheduler, task from the head (in FIFO order) is stolen
121
+ * * [STEAL_BLOCKING_ONLY] is mode for stealing *an arbitrary* blocking task, which is used by the scheduler when helping in Dispatchers.IO mode
122
+ * * [STEAL_CPU_ONLY] is a kludge for `runSingleTaskFromCurrentSystemDispatcher`
110
123
*/
111
- fun trySteal (stolenTaskRef : ObjectRef <Task ?>): Long {
112
- val task = pollBuffer()
124
+ fun trySteal (stealingMode : StealingMode , stolenTaskRef : ObjectRef <Task ?>): Long {
125
+ val task = when (stealingMode) {
126
+ STEAL_ANY -> pollBuffer()
127
+ else -> stealWithExclusiveMode(stealingMode)
128
+ }
129
+
113
130
if (task != null ) {
114
131
stolenTaskRef.element = task
115
132
return TASK_STOLEN
116
133
}
117
- return tryStealLastScheduled(stolenTaskRef, blockingOnly = false )
134
+ return tryStealLastScheduled(stealingMode, stolenTaskRef )
118
135
}
119
136
120
- fun tryStealBlocking (stolenTaskRef : ObjectRef <Task ?>): Long {
137
+ // Steal only tasks of a particular kind, potentially invoking full queue scan
138
+ private fun stealWithExclusiveMode (stealingMode : StealingMode ): Task ? {
121
139
var start = consumerIndex.value
122
140
val end = producerIndex.value
123
-
124
- while (start != end && blockingTasksInBuffer.value > 0 ) {
125
- stolenTaskRef.element = tryExtractBlockingTask(start++ ) ? : continue
126
- return TASK_STOLEN
141
+ val onlyBlocking = stealingMode == STEAL_BLOCKING_ONLY
142
+ // Bail out if there is no blocking work for us
143
+ while (start != end) {
144
+ if (onlyBlocking && blockingTasksInBuffer.value == 0 ) return null
145
+ return tryExtractFromTheMiddle(start++ , onlyBlocking) ? : continue
127
146
}
128
- return tryStealLastScheduled(stolenTaskRef, blockingOnly = true )
147
+
148
+ return null
129
149
}
130
150
131
151
// Polls for blocking task, invoked only by the owner
132
- fun pollBlocking (): Task ? {
152
+ // NB: ONLY for runSingleTask method
153
+ fun pollBlocking (): Task ? = pollWithExclusiveMode(onlyBlocking = true /* only blocking */ )
154
+
155
+ // Polls for CPU task, invoked only by the owner
156
+ // NB: ONLY for runSingleTask method
157
+ fun pollCpu (): Task ? = pollWithExclusiveMode(onlyBlocking = false /* only cpu */ )
158
+
159
+ private fun pollWithExclusiveMode (/* Only blocking OR only CPU */ onlyBlocking : Boolean ): Task ? {
133
160
while (true ) { // Poll the slot
134
161
val lastScheduled = lastScheduledTask.value ? : break
135
- if (! lastScheduled.isBlocking) break
162
+ if (lastScheduled.isBlocking != onlyBlocking ) break
136
163
if (lastScheduledTask.compareAndSet(lastScheduled, null )) {
137
164
return lastScheduled
138
165
} // Failed -> someone else stole it
139
166
}
140
167
168
+ // Failed to poll the slot, scan the queue
141
169
val start = consumerIndex.value
142
170
var end = producerIndex.value
143
-
144
- while (start != end && blockingTasksInBuffer.value > 0 ) {
145
- val task = tryExtractBlockingTask(-- end)
171
+ // Bail out if there is no blocking work for us
172
+ while (start != end) {
173
+ if (onlyBlocking && blockingTasksInBuffer.value == 0 ) return null
174
+ val task = tryExtractFromTheMiddle(-- end, onlyBlocking)
146
175
if (task != null ) {
147
176
return task
148
177
}
149
178
}
150
179
return null
151
180
}
152
181
153
- private fun tryExtractBlockingTask (index : Int ): Task ? {
182
+ private fun tryExtractFromTheMiddle (index : Int , onlyBlocking : Boolean ): Task ? {
154
183
val arrayIndex = index and MASK
155
184
val value = buffer[arrayIndex]
156
- if (value != null && value.isBlocking && buffer.compareAndSet(arrayIndex, value, null )) {
157
- blockingTasksInBuffer.decrementAndGet()
185
+ if (value != null && value.isBlocking == onlyBlocking && buffer.compareAndSet(arrayIndex, value, null )) {
186
+ if (onlyBlocking) blockingTasksInBuffer.decrementAndGet()
158
187
return value
159
188
}
160
189
return null
@@ -170,10 +199,12 @@ internal class WorkQueue {
170
199
/* *
171
200
* Contract on return value is the same as for [trySteal]
172
201
*/
173
- private fun tryStealLastScheduled (stolenTaskRef : ObjectRef <Task ?>, blockingOnly : Boolean ): Long {
202
+ private fun tryStealLastScheduled (stealingMode : StealingMode , stolenTaskRef : ObjectRef <Task ?>): Long {
174
203
while (true ) {
175
204
val lastScheduled = lastScheduledTask.value ? : return NOTHING_TO_STEAL
176
- if (blockingOnly && ! lastScheduled.isBlocking) return NOTHING_TO_STEAL
205
+ if ((lastScheduled.maskForStealingMode and stealingMode) == 0 ) {
206
+ return NOTHING_TO_STEAL
207
+ }
177
208
178
209
// TODO time wraparound ?
179
210
val time = schedulerTimeSource.nanoTime()
0 commit comments