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
//! # Thread Slot
//! What a worker and a sleep thread both keep about the thread
//! behind them, and the moves both make on it
use crate::{
constants::NO_TASK,
modules::{address_lock, faults, worker_state::WorkerState},
};
use std::{
sync::atomic::{AtomicU32, AtomicUsize, Ordering},
thread,
};
/// A kind of thread the pool runs behind a slot
pub(crate) trait PoolThread: Sync + 'static {
/// The part of the slot every kind of thread keeps
fn slot(&self) -> &ThreadSlot;
/// Hands the slot back once the thread's loop has broken on
/// its own
fn left(&'static self);
}
/// Where one thread is, what it is holding, and how long it has
/// been idle
#[repr(C)]
pub(crate) struct ThreadSlot {
/// Where the thread is, and the address it parks on
state: AtomicU32,
/// The task being run right now, as its id plus one, or zero for
/// none
///
/// Kept here so a thread that dies leaves a note of which task
/// went with it
current: AtomicUsize,
/// Manager ticks this thread has been idle for
idle_ticks: AtomicU32,
}
impl ThreadSlot {
/// A slot with no thread behind it yet
pub(crate) const fn new() -> Self {
Self {
state: AtomicU32::new(WorkerState::Empty as u32),
current: AtomicUsize::new(0),
idle_ticks: AtomicU32::new(0),
}
}
/// The current state
#[inline(always)]
pub(crate) fn state(&self) -> WorkerState {
WorkerState::from_u32(self.state.load(Ordering::Acquire))
}
/// Whether a thread is behind this slot at all
#[inline(always)]
pub(crate) fn alive(&self) -> bool {
self.state().alive()
}
/// Whether the thread is inside a task right now
#[inline(always)]
pub(crate) fn busy(&self) -> bool {
self.state().busy()
}
/// Notes another idle tick, and says how many in a row
#[inline(always)]
pub(crate) fn idled(&self) -> u32 {
self.idle_ticks.fetch_add(1, Ordering::Relaxed) + 1
}
/// Forgets how long the thread has been idle
#[inline(always)]
pub(crate) fn busied(&self) {
self.idle_ticks.store(0, Ordering::Relaxed);
}
/// Claims this slot so a thread can be started into it
pub(crate) fn claim(&self) -> bool {
self.state
.compare_exchange(
WorkerState::Empty as u32,
WorkerState::Starting as u32,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
/// Puts a thread behind this slot, running `run` on `owner`
///
/// `stack` reserves a stack of that many bytes, or the default
///
/// ## Returns
/// Whether the thread started. If it didn't, the slot is freed
/// again
pub(crate) fn start<O: Sync>(
&self,
name: &str,
stack: Option<usize>,
owner: &'static O,
run: fn(&'static O),
) -> bool {
// A refusal a test asked for looks the same as the kernel's own
if !faults::spawn_refused() {
let mut builder = thread::Builder::new().name(String::from(name));
if let Some(stack) = stack {
builder = builder.stack_size(stack);
}
if builder.spawn(move || run(owner)).is_ok() {
return true;
}
}
self.empty();
false
}
/// Moves a thread that has just come up from starting to idle
///
/// Exchanged, so a stop that arrived before the thread was up
/// isn't lost
#[inline(always)]
pub(crate) fn started(&self) {
let _ = self.state.compare_exchange(
WorkerState::Starting as u32,
WorkerState::Idle as u32,
Ordering::AcqRel,
Ordering::Relaxed,
);
}
/// Asks the thread to stop once it has put down whatever it is
/// holding
///
/// A task already running finishes normally
pub(crate) fn stop(&self) {
let stopping =
self.state
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |state| {
match WorkerState::from_u32(state).alive() {
true => Some(WorkerState::Stopping as u32),
false => None,
}
})
.is_ok();
if stopping {
address_lock::wake(address_lock::address(&self.state));
}
}
/// Wakes the thread if it is asleep
///
/// The state leaves `Parked` before the wake goes out, so a
/// thread about to sleep doesn't
///
/// ## Returns
/// Whether this caller took the thread out of its park. Only
/// one caller can, per park
#[inline(always)]
pub(crate) fn wake(&self) -> bool {
let claimed = self
.state
.compare_exchange(
WorkerState::Parked as u32,
WorkerState::Idle as u32,
Ordering::SeqCst,
Ordering::Relaxed,
)
.is_ok();
address_lock::wake(address_lock::address(&self.state));
claimed
}
/// Records the task the thread is about to run
///
/// Nothing between taking the task and this store may unwind,
/// or the id would be lost with the thread
#[inline(always)]
pub(crate) fn hold(&self, id: usize) {
self.current.store(id + 1, Ordering::Release);
}
/// Records that the thread is no longer holding a task
#[inline(always)]
pub(crate) fn put_down(&self) {
self.current.store(0, Ordering::Release);
}
/// Takes the note of which task the thread was holding
///
/// ## Returns
/// The task's id, or `NO_TASK`
#[inline(always)]
pub(crate) fn take_held(&self) -> usize {
match self.current.swap(0, Ordering::AcqRel) {
0 => NO_TASK,
held => held - 1,
}
}
/// Moves from idle into running a task
///
/// ## Returns
/// Whether it did. `false` means a stop got there first
#[inline(always)]
pub(crate) fn begin_task(&self) -> bool {
self.state
.compare_exchange(
WorkerState::Idle as u32,
WorkerState::Running as u32,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
/// Moves back to idle once a task is done, unless something
/// else changed the state meanwhile
#[inline(always)]
pub(crate) fn end_task(&self) {
let _ = self.state.compare_exchange(
WorkerState::Running as u32,
WorkerState::Idle as u32,
Ordering::AcqRel,
Ordering::Relaxed,
);
}
/// Leaves the slot empty, ready to be claimed again
#[inline(always)]
pub(crate) fn empty(&self) {
self.state
.store(WorkerState::Empty as u32, Ordering::Release);
}
/// Marks the slot dead and wakes anything waiting on it
pub(crate) fn mark_dead(&self) {
self.state
.store(WorkerState::Dead as u32, Ordering::Release);
address_lock::wake(address_lock::address(&self.state));
}
/// Takes responsibility for clearing up after a dead thread
///
/// ## Returns
/// Whether this caller should do it. Only one caller ever gets
/// `true` per death
pub(crate) fn claim_recovery(&self) -> bool {
self.state
.compare_exchange(
WorkerState::Dead as u32,
WorkerState::Recovering as u32,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
/// Blocks until there is something to do or somebody says to
/// stop
///
/// Publishes that it is parking before its last look for work,
/// and whatever queues work checks for parked threads after it
/// has queued, so work can't slip between the two
pub(crate) fn park(
&self,
parked_in: impl Fn(),
parked_out: impl Fn(),
has_work: impl FnOnce() -> bool,
) {
parked_in();
// Exchanged, so a stop that already spent its wake isn't
// written over. `SeqCst`, as the handshake above needs
if self
.state
.compare_exchange(
WorkerState::Idle as u32,
WorkerState::Parked as u32,
Ordering::SeqCst,
Ordering::SeqCst,
)
.is_err()
{
parked_out();
return;
}
if has_work() {
// Compared, so a stop that landed in this window survives too
let _ = self.state.compare_exchange(
WorkerState::Parked as u32,
WorkerState::Idle as u32,
Ordering::SeqCst,
Ordering::SeqCst,
);
parked_out();
return;
}
let _ = address_lock::wait(
address_lock::address(&self.state),
WorkerState::Parked as u32,
);
parked_out();
// Only back to idle if nothing else changed the state while
// it slept
let _ = self.state.compare_exchange(
WorkerState::Parked as u32,
WorkerState::Idle as u32,
Ordering::AcqRel,
Ordering::Relaxed,
);
}
}