1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
//! # Injector
//! The queue every spawned task lands in, and the one workers
//! fall back to when their own is empty
//!
//! Unbounded. Each band is two stacks: reversing the
//! pushed side when the served side runs dry gives first in,
//! first out order
use crate::{
constants::{INDEX_MASK, PRIORITY_BANDS, STARVE_RELIEF, TAG_SHIFT},
executor,
modules::task_data::QUEUED_SHARED,
};
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
/// Every task waiting for a worker to pick it up
pub(crate) struct Injector {
/// Where new tasks are pushed, newest first
///
/// Untagged, since these are only ever taken whole by `flip`
incoming: [AtomicUsize; PRIORITY_BANDS],
/// Reversed and ready to serve, oldest first
///
/// Tagged against ABA, since every worker pops from it one
/// node at a time
ready: [AtomicUsize; PRIORITY_BANDS],
/// Whether a reversal is under way on a band
///
/// One at a time per band, so a reversal can store its list
/// rather than merge it
flipping: [AtomicBool; PRIORITY_BANDS],
/// Tasks pushed but not yet taken
len: AtomicUsize,
/// Oldest first pops still owed, topped up by the manager when
/// the queue is starving
relief: AtomicU32,
}
impl Injector {
/// An empty injector
pub(crate) const fn new() -> Self {
Self {
incoming: [const { AtomicUsize::new(0) }; PRIORITY_BANDS],
ready: [const { AtomicUsize::new(0) }; PRIORITY_BANDS],
flipping: [const { AtomicBool::new(false) }; PRIORITY_BANDS],
len: AtomicUsize::new(0),
relief: AtomicU32::new(0),
}
}
/// Queues a task in the band its priority picks
///
/// ## Returns
/// Whether it was queued. `false` means the id has no live
/// task behind it
pub(crate) fn push(&self, id: usize) -> bool {
let Some(data) = executor::slot(id) else {
return false;
};
let band = data.band().min(PRIORITY_BANDS - 1);
let index = id + 1;
// Marked before it can be found, so whoever pops it can claim it
data.mark_queued(QUEUED_SHARED);
// Counted before it is published, so a pop can never take the
// count below zero. `SeqCst` because a parking worker reads it
// against its own announcement
self.len.fetch_add(1, Ordering::SeqCst);
loop {
let head = self.incoming[band].load(Ordering::Acquire);
data.set_queue_next(head);
if self.incoming[band]
.compare_exchange_weak(head, index, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
return true;
}
}
}
/// Takes the task that should be served next
///
/// Highest band first, unless the queue is starving, when the
/// oldest task can go first
pub(crate) fn pop(&self) -> Option<usize> {
self.pop_banded().map(|(id, _)| id)
}
/// The same pop, saying which band it came out of
pub(crate) fn pop_banded(&self) -> Option<(usize, usize)> {
// Relief prefers the band holding the oldest task, and falls
// back to the normal order if that band is empty
if self.relief.load(Ordering::Relaxed) != 0 {
if let Some(band) = self.oldest_band() {
if let Some(id) = self.take(band) {
self.spend_relief();
return Some((id, band));
}
}
}
for band in (0..PRIORITY_BANDS).rev() {
if let Some(id) = self.take(band) {
return Some((id, band));
}
}
None
}
/// Takes the next task out of one band and no other
#[inline(always)]
pub(crate) fn pop_from(&self, band: usize) -> Option<usize> {
self.take(band)
}
/// Tasks queued and not yet taken
///
/// Approximate, and only ever high, never low
#[inline(always)]
pub(crate) fn len(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
/// Whether anything is waiting at all
///
/// `SeqCst`, as a worker's park handshake needs
#[inline(always)]
pub(crate) fn is_empty(&self) -> bool {
self.len.load(Ordering::SeqCst) == 0
}
/// Says whether the oldest queued task is starving, granting or
/// clearing a budget of oldest first pops
#[inline(always)]
pub(crate) fn set_starving(&self, starving: bool) {
let budget = match starving {
true => STARVE_RELIEF,
false => 0,
};
self.relief.store(budget, Ordering::Relaxed);
}
/// Spends one unit of the starvation budget, once a relief pop
/// has come back with a task
#[inline(always)]
fn spend_relief(&self) {
let _ = self
.relief
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |left| match left {
0 => None,
_ => Some(left - 1),
});
}
/// The oldest task waiting in any band, and the band it is in
///
/// Reads the served side only, so it wants calling after a
/// flip
pub(crate) fn oldest(&self) -> Option<(usize, usize)> {
let mut oldest: Option<(usize, usize, u64)> = None;
for band in 0..PRIORITY_BANDS {
let index = self.ready[band].load(Ordering::Acquire) & INDEX_MASK;
if index == 0 {
continue;
}
let id = index - 1;
let Some(data) = executor::slot(id) else {
continue;
};
let stamp = data.priority_sequence();
if oldest.is_none_or(|(_, _, best)| stamp < best) {
oldest = Some((band, id, stamp));
}
}
oldest.map(|(band, id, _)| (band, id))
}
/// The band holding the oldest queued task
#[inline(always)]
fn oldest_band(&self) -> Option<usize> {
self.oldest().map(|(band, _)| band)
}
/// Moves the oldest task in a band onto the pushed side of the
/// band above
///
/// The task keeps its own priority class
pub(crate) fn promote(&self, band: usize) {
if band + 1 >= PRIORITY_BANDS {
return;
}
let Some(id) = self.pop_ready(band) else {
return;
};
// `pop_ready` only returns live tasks, and this one is only
// changing band, so the count stays as it is
let Some(data) = executor::slot(id) else {
self.len.fetch_sub(1, Ordering::Relaxed);
return;
};
let index = id + 1;
loop {
let head = self.incoming[band + 1].load(Ordering::Acquire);
data.set_queue_next(head);
if self.incoming[band + 1]
.compare_exchange_weak(head, index, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
return;
}
}
}
/// Reverses every band's pushed side onto its served side, so
/// `oldest` can see everything queued
pub(crate) fn refill(&self) {
for band in 0..PRIORITY_BANDS {
self.flip(band);
}
}
/// Empties every band into a list of ids, for tearing the pool
/// down
pub(crate) fn drain(&self) -> Vec<usize> {
let mut drained = Vec::new();
while let Some(id) = self.pop() {
drained.push(id);
}
drained
}
/// Takes from one band, reversing its pushed side if the
/// served side has run dry
///
/// A band another thread is already reversing is skipped, not
/// waited for
fn take(&self, band: usize) -> Option<usize> {
loop {
let id = match self.pop_ready(band) {
Some(id) => id,
None => {
if !self.flip(band) {
return None;
}
self.pop_ready(band)?
}
};
self.len.fetch_sub(1, Ordering::Relaxed);
// Only this queue takes what it links, so this always holds.
// Checked anyway, so nothing could ever run twice
if executor::slot(id).is_some_and(|data| data.claim_queued(QUEUED_SHARED)) {
return Some(id);
}
}
}
/// Pops one task off a band's served side
///
/// The tag is bumped on the way out, against ABA
fn pop_ready(&self, band: usize) -> Option<usize> {
loop {
let head = self.ready[band].load(Ordering::Acquire);
let index = head & INDEX_MASK;
if index == 0 {
return None;
}
let id = index - 1;
// Through `queue_link`, since a retired node still has to be
// stepped over
let Some(next) = executor::queue_link(id) else {
// No memory behind this id, so nothing behind it can be
// reached. The band is emptied rather than wedged on it
let tag = (head >> TAG_SHIFT).wrapping_add(1);
let _ = self.ready[band].compare_exchange(
head,
tag << TAG_SHIFT,
Ordering::AcqRel,
Ordering::Relaxed,
);
return None;
};
let tag = (head >> TAG_SHIFT).wrapping_add(1);
let new = (tag << TAG_SHIFT) | next;
if self.ready[band]
.compare_exchange_weak(head, new, Ordering::AcqRel, Ordering::Relaxed)
.is_err()
{
continue;
}
// A retired task is counted out and stepped over, rather than
// left at the head to wedge the band
if executor::slot(id).is_none() {
self.len.fetch_sub(1, Ordering::Relaxed);
continue;
}
return Some(id);
}
}
/// Turns a band's pushed side into its served side
///
/// ## Returns
/// Whether the served side is worth looking at again
///
/// Only a flip fills the served side, and only one runs at a
/// time, so its list is stored rather than merged
fn flip(&self, band: usize) -> bool {
if self.flipping[band].swap(true, Ordering::AcqRel) {
// Somebody else is reversing it
return true;
}
let head = self.ready[band].load(Ordering::Acquire);
// Filled while this was taking the lock
if head & INDEX_MASK != 0 {
self.flipping[band].store(false, Ordering::Release);
return true;
}
let mut cursor = self.incoming[band].swap(0, Ordering::AcqRel);
if cursor == 0 {
self.flipping[band].store(false, Ordering::Release);
return false;
}
let mut reversed = 0;
while cursor != 0 {
// Can't happen: a queued id always has a mapped slot
let Some(next) = executor::queue_link(cursor - 1) else {
break;
};
executor::set_queue_link(cursor - 1, reversed);
reversed = cursor;
cursor = next;
}
// Tag bumped, so a popper holding a stale head can't land
let tag = (head >> TAG_SHIFT).wrapping_add(1);
self.ready[band].store((tag << TAG_SHIFT) | reversed, Ordering::Release);
self.flipping[band].store(false, Ordering::Release);
reversed != 0
}
}