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