1use core::sync::atomic::{AtomicU8, Ordering};
4
5use crate::thread::TaskError;
6
7const STATE_MASK: u8 = 0b111;
8const WAKE_PENDING: u8 = 1 << 3;
9const PARK_NOTIFIED: u8 = 1 << 4;
10const WAKE_STATE_PUBLISHED: u8 = WAKE_PENDING | PARK_NOTIFIED;
11
12#[repr(u8)]
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum ThreadState {
16 New = 0,
18 Running = 2,
20 Parking = 3,
22 Blocked = 4,
24 Waking = 5,
26 Exited = 6,
28}
29
30#[derive(Debug)]
32pub(crate) struct ThreadLifecycle {
33 state: AtomicU8,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub(crate) struct WakePublication {
38 state: ThreadState,
39 already_pending: bool,
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub(crate) enum ParkPublication {
44 Notified,
45 Blocked,
46}
47
48impl WakePublication {
49 pub(crate) const fn state(self) -> ThreadState {
50 self.state
51 }
52
53 pub(crate) const fn already_pending(self) -> bool {
54 self.already_pending
55 }
56}
57
58impl ThreadLifecycle {
59 pub(crate) const fn new() -> Self {
60 Self {
61 state: AtomicU8::new(ThreadState::New as u8),
62 }
63 }
64
65 #[track_caller]
66 pub(crate) fn state(&self) -> ThreadState {
67 decode_state(self.state.load(Ordering::Acquire))
68 }
69
70 #[track_caller]
71 pub(crate) fn transition(&self, next: ThreadState) -> Result<(), TaskError> {
72 let mut observed = self.state.load(Ordering::Acquire);
73 loop {
74 let current = decode_state(observed);
75 if !transition_is_valid(current, next) {
76 return Err(TaskError::InvalidTransition {
77 from: current,
78 to: next,
79 });
80 }
81 let retained = if current == ThreadState::New {
86 0
87 } else {
88 observed & !STATE_MASK
89 };
90 let updated = retained | next as u8;
91 match self.state.compare_exchange_weak(
92 observed,
93 updated,
94 Ordering::AcqRel,
95 Ordering::Acquire,
96 ) {
97 Ok(_) => return Ok(()),
98 Err(updated) => observed = updated,
99 }
100 }
101 }
102
103 #[track_caller]
104 pub(crate) fn publish_wake(&self) -> WakePublication {
105 let previous = self.state.fetch_or(WAKE_STATE_PUBLISHED, Ordering::AcqRel);
106 WakePublication {
107 state: decode_state(previous),
108 already_pending: previous & WAKE_PENDING != 0,
109 }
110 }
111
112 #[cfg(test)]
113 pub(crate) fn consume_wake(&self, preserve_park_notification: bool) -> bool {
114 let consumed = if preserve_park_notification {
115 WAKE_PENDING
116 } else {
117 WAKE_STATE_PUBLISHED
118 };
119 self.state.fetch_and(!consumed, Ordering::AcqRel) & WAKE_PENDING != 0
120 }
121
122 pub(crate) fn discard_failed_wake(&self) {
123 self.state
124 .fetch_and(!WAKE_STATE_PUBLISHED, Ordering::AcqRel);
125 }
126
127 pub(crate) fn take_park_notification(&self) -> bool {
128 self.state
129 .fetch_and(!WAKE_STATE_PUBLISHED, Ordering::AcqRel)
130 & PARK_NOTIFIED
131 != 0
132 }
133
134 pub(crate) fn consume_wake_and_transition(
139 &self,
140 preserve_park_notification: bool,
141 next: Option<ThreadState>,
142 ) -> (ThreadState, bool) {
143 let mut observed = self.state.load(Ordering::Acquire);
144 loop {
145 let current = decode_state(observed);
146 let pending = observed & WAKE_PENDING != 0;
147 let consumed = if preserve_park_notification && current == ThreadState::Parking {
148 WAKE_PENDING
149 } else {
150 WAKE_STATE_PUBLISHED
151 };
152 let mut updated = observed & !consumed;
153 if pending && next == Some(ThreadState::Waking) && current == ThreadState::Blocked {
154 updated = (updated & !STATE_MASK) | ThreadState::Waking as u8;
155 }
156 if updated == observed {
157 return (current, pending);
158 }
159 match self.state.compare_exchange_weak(
160 observed,
161 updated,
162 Ordering::AcqRel,
163 Ordering::Acquire,
164 ) {
165 Ok(_) => return (current, pending),
166 Err(next_observed) => observed = next_observed,
167 }
168 }
169 }
170
171 #[track_caller]
173 pub(crate) fn publish_blocked_from_parking(&self) -> Result<ParkPublication, TaskError> {
174 let mut observed = self.state.load(Ordering::Acquire);
175 loop {
176 let current = decode_state(observed);
177 if current != ThreadState::Parking {
178 return Err(TaskError::InvalidTransition {
179 from: current,
180 to: ThreadState::Blocked,
181 });
182 }
183 let (updated, publication) = if observed & PARK_NOTIFIED != 0 {
184 (
185 (observed & !(STATE_MASK | WAKE_STATE_PUBLISHED)) | ThreadState::Running as u8,
186 ParkPublication::Notified,
187 )
188 } else {
189 (
190 (observed & !(STATE_MASK | WAKE_STATE_PUBLISHED)) | ThreadState::Blocked as u8,
191 ParkPublication::Blocked,
192 )
193 };
194 match self.state.compare_exchange_weak(
195 observed,
196 updated,
197 Ordering::AcqRel,
198 Ordering::Acquire,
199 ) {
200 Ok(_) => return Ok(publication),
201 Err(updated) => observed = updated,
202 }
203 }
204 }
205}
206
207#[track_caller]
208pub(crate) fn decode_state(packed: u8) -> ThreadState {
209 match packed & STATE_MASK {
210 0 => ThreadState::New,
211 2 => ThreadState::Running,
212 3 => ThreadState::Parking,
213 4 => ThreadState::Blocked,
214 5 => ThreadState::Waking,
215 6 => ThreadState::Exited,
216 _ => panic!("invalid thread lifecycle publication: raw={packed:#04x}"),
217 }
218}
219
220pub(crate) const fn transition_is_valid(from: ThreadState, to: ThreadState) -> bool {
221 matches!(
222 (from, to),
223 (ThreadState::New, ThreadState::Running | ThreadState::Exited)
224 | (
225 ThreadState::Running,
226 ThreadState::Parking | ThreadState::Exited
227 )
228 | (
229 ThreadState::Parking,
230 ThreadState::Running | ThreadState::Blocked | ThreadState::Waking
231 )
232 | (
233 ThreadState::Blocked,
234 ThreadState::Running | ThreadState::Waking | ThreadState::Exited
237 )
238 | (
239 ThreadState::Waking,
240 ThreadState::Running | ThreadState::Exited
241 )
242 )
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn accepts_the_documented_wake_transition() {
251 let lifecycle = ThreadLifecycle::new();
252 lifecycle.transition(ThreadState::Running).unwrap();
253 lifecycle.transition(ThreadState::Parking).unwrap();
254 lifecycle.transition(ThreadState::Waking).unwrap();
255 lifecycle.transition(ThreadState::Running).unwrap();
256 assert_eq!(lifecycle.state(), ThreadState::Running);
257 }
258
259 #[test]
260 fn rejects_runnable_to_blocked_shortcut() {
261 assert!(!transition_is_valid(
262 ThreadState::Running,
263 ThreadState::Blocked
264 ));
265 }
266
267 #[test]
268 fn wake_publication_atomically_defeats_blocked_publication() {
269 let lifecycle = ThreadLifecycle::new();
270 lifecycle.transition(ThreadState::Running).unwrap();
271 lifecycle.transition(ThreadState::Parking).unwrap();
272 assert_eq!(lifecycle.publish_wake().state(), ThreadState::Parking);
273 assert_eq!(
274 lifecycle.publish_blocked_from_parking().unwrap(),
275 ParkPublication::Notified
276 );
277 assert_eq!(lifecycle.state(), ThreadState::Running);
278 assert!(!lifecycle.consume_wake(false));
279 }
280
281 #[test]
282 fn blocked_publication_wins_before_late_wake() {
283 let lifecycle = ThreadLifecycle::new();
284 lifecycle.transition(ThreadState::Running).unwrap();
285 lifecycle.transition(ThreadState::Parking).unwrap();
286 assert_eq!(
287 lifecycle.publish_blocked_from_parking().unwrap(),
288 ParkPublication::Blocked
289 );
290 assert_eq!(lifecycle.publish_wake().state(), ThreadState::Blocked);
291 }
292
293 #[test]
294 fn on_rq_wake_transitions_directly_from_blocked_to_running() {
295 let lifecycle = ThreadLifecycle::new();
296 lifecycle.transition(ThreadState::Running).unwrap();
297 lifecycle.transition(ThreadState::Parking).unwrap();
298 assert_eq!(
299 lifecycle.publish_blocked_from_parking().unwrap(),
300 ParkPublication::Blocked
301 );
302
303 lifecycle.transition(ThreadState::Running).unwrap();
304
305 assert_eq!(lifecycle.state(), ThreadState::Running);
306 }
307
308 #[test]
309 #[should_panic(expected = "invalid thread lifecycle publication: raw=0x0f")]
310 fn invalid_lifecycle_publication_reports_the_packed_byte() {
311 let lifecycle = ThreadLifecycle::new();
312 lifecycle
313 .state
314 .store(WAKE_PENDING | STATE_MASK, Ordering::Relaxed);
315
316 let _ = lifecycle.state();
317 }
318
319 #[test]
320 #[should_panic(expected = "invalid thread lifecycle publication: raw=0x01")]
321 fn reserved_state_encoding_is_rejected() {
322 let lifecycle = ThreadLifecycle::new();
323 lifecycle.state.store(1, Ordering::Relaxed);
324
325 let _ = lifecycle.state();
326 }
327}
328
329#[cfg(all(test, not(miri)))]
330mod loom_tests {
331 use loom::{
332 sync::{
333 Arc,
334 atomic::{AtomicUsize, Ordering},
335 },
336 thread,
337 };
338
339 const STATE_MASK: usize = 0b111;
340 const RUNNING: usize = 2;
341 const PARKING: usize = 3;
342 const BLOCKED: usize = 4;
343 const WAKE_PENDING: usize = 1 << 3;
344 const PARK_NOTIFIED: usize = 1 << 4;
345 const WAKE_STATE_PUBLISHED: usize = WAKE_PENDING | PARK_NOTIFIED;
346
347 #[test]
348 fn wake_publication_cannot_strand_a_parking_thread() {
349 loom::model(|| {
350 let lifecycle = Arc::new(AtomicUsize::new(PARKING));
351
352 let parker = {
353 let lifecycle = Arc::clone(&lifecycle);
354 thread::spawn(move || {
355 let mut observed = lifecycle.load(Ordering::Acquire);
356 loop {
357 assert_eq!(observed & STATE_MASK, PARKING);
358 let updated = if observed & PARK_NOTIFIED != 0 {
359 RUNNING
360 } else {
361 BLOCKED
362 };
363 match lifecycle.compare_exchange_weak(
364 observed,
365 updated,
366 Ordering::AcqRel,
367 Ordering::Acquire,
368 ) {
369 Ok(_) => break,
370 Err(updated) => observed = updated,
371 }
372 }
373 })
374 };
375 let waker = {
376 let lifecycle = Arc::clone(&lifecycle);
377 thread::spawn(move || {
378 let previous = lifecycle.fetch_or(WAKE_STATE_PUBLISHED, Ordering::AcqRel);
379 if previous & STATE_MASK == BLOCKED {
380 let observed = lifecycle.fetch_and(!WAKE_STATE_PUBLISHED, Ordering::AcqRel);
381 assert_ne!(observed & WAKE_PENDING, 0);
382 lifecycle
383 .compare_exchange(BLOCKED, RUNNING, Ordering::AcqRel, Ordering::Acquire)
384 .unwrap();
385 }
386 })
387 };
388
389 parker.join().unwrap();
390 waker.join().unwrap();
391 assert_ne!(
392 lifecycle.load(Ordering::Acquire) & STATE_MASK,
393 BLOCKED,
394 "a wake racing Parking-to-Blocked must resume or activate the thread"
395 );
396 });
397 }
398}