shuttle_engine/runtime/task/mod.rs
1use crate::backtrace_enabled;
2use crate::current::get_name_for_task;
3use crate::runtime::execution::{ExecutionState, TASK_ID_TO_TAGS};
4use crate::runtime::storage::{AlreadyDestructedError, StorageKey, StorageMap};
5use crate::runtime::task::clock::VectorClock;
6use crate::runtime::task::labels::Labels;
7use crate::runtime::thread;
8use crate::runtime::thread::continuation::{
9 ContinuationInput, ContinuationOutput, ContinuationPool, PooledContinuation,
10};
11use crate::sync_types::{ResourceSignature, ResourceType};
12use crate::thread_support::LocalKey;
13use bitvec::prelude::*;
14use corosensei::Yielder;
15use std::any::Any;
16use std::backtrace::Backtrace;
17use std::cell::RefCell;
18use std::collections::HashMap;
19use std::fmt::Debug;
20use std::future::Future;
21use std::hash::{DefaultHasher, Hash, Hasher};
22use std::panic::Location;
23use std::pin::Pin;
24use std::rc::{Rc, Weak};
25use std::sync::Arc;
26use std::task::{Context, Poll, Waker};
27use tracing::{error_span, event, field, Level, Span};
28
29pub mod clock;
30pub mod labels;
31pub mod waker;
32use waker::make_waker;
33
34// A note on terminology: we have competing notions of threads floating around. Here's the
35// convention for disambiguating them:
36// * A "thread" is a user-level unit of concurrency. User code creates threads, passes data
37// between them, etc.
38// * A "future" is another user-level unit of concurrency, corresponding directly to Rust's notion
39// in std::future::Future. A future has a single method `poll` that can be used to resume
40// executing its computation. Both futures and threads are implemented in Task,
41// which wraps a continuation that is resumed when the task is scheduled.
42// * A "task" is the Shuttle executor's reflection of a user-level unit of concurrency. Each task
43// has a corresponding continuation, which is the user-level code it runs, as well as a state like
44// "blocked", "runnable", etc. Scheduling algorithms take as input the state of all tasks
45// and decide which task should execute next. A context switch is when one task stops executing
46// and another begins.
47// * A "continuation" is a low-level implementation of green threading for concurrency. Each
48// Task contains a corresponding continuation. When the Shuttle executor context switches to a
49// Task, the executor resumes that task's continuation until it yields, which happens when its
50// thread decides it might want to context switch (e.g., because it's blocked on a lock).
51
52pub const DEFAULT_INLINE_TASKS: usize = 16;
53
54/// A reserved label that is used to assign readable names to tasks for debugging.
55///
56/// To make debugging easier, if a task is assigned a `TaskName(s)` Label,
57/// Shuttle will display the String `s` in addition to the `TaskId` in debug output.
58#[derive(Clone, PartialEq, Eq)]
59pub struct TaskName(String);
60
61impl From<String> for TaskName {
62 fn from(s: String) -> Self {
63 Self(s)
64 }
65}
66
67impl From<&str> for TaskName {
68 fn from(s: &str) -> Self {
69 Self(String::from(s))
70 }
71}
72
73impl std::fmt::Debug for TaskName {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{}", self.0)
76 }
77}
78
79impl From<TaskName> for String {
80 fn from(task_name: TaskName) -> Self {
81 task_name.0
82 }
83}
84
85impl<'a> From<&'a TaskName> for &'a String {
86 fn from(task_name: &'a TaskName) -> Self {
87 &task_name.0
88 }
89}
90
91/// A special label that can be used to set labels for a task when it is spawned.
92///
93/// By default, when a task or thread T is spawned, it inherits all labels from its parent.
94/// It's often useful to modify or add new Labels to T. One approach is to put label changes
95/// at the beginning of the closure that is passed to `spawn`, but this approach has the drawback
96/// that the changes are applied only when T is first selected for execution, and the closure
97/// is invoked. To overcome this drawback, we introduce the `ChildLabelFn` label. If a parent
98/// task or thread has a `ChildLabelFn` set when it spawns a new child task or thread, the
99/// child's label set at spawn time will be modified by applying the function inside the `ChildLabelFn`.
100///
101/// # Example
102/// The following example shows how a `ChildLabelFn` can be used to set up names for the next child(ren)
103/// that will be spawned by a parent task (see `shuttle/tests/basic/labels.rs` for runnable versions).
104/// ```ignore
105/// # use shuttle_engine::current::{me, set_label_for_task, get_name_for_task, ChildLabelFn, TaskName};
106/// # use std::sync::Arc;
107/// // In the parent, set up a `ChildLabelFn` that assigns a name to the child task
108/// shuttle::check_dfs(|| {
109/// set_label_for_task(me(), ChildLabelFn(Arc::new(|_task_id, labels| { labels.insert(TaskName::from("ChildTask")); })));
110/// shuttle::thread::spawn(|| {
111/// assert_eq!(get_name_for_task(me()).unwrap(), TaskName::from("ChildTask")); // child task already has the name
112/// // ... rest of child
113/// }).join().unwrap();
114/// }, None);
115/// ```
116#[derive(Clone)]
117#[allow(clippy::type_complexity)]
118pub struct ChildLabelFn(pub Arc<dyn Fn(TaskId, &mut Labels) + 'static>);
119
120impl Debug for ChildLabelFn {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "ChildLabelFn")
123 }
124}
125
126/// A `Tag` is an optional piece of metadata associated with a task (a thread or spawned future) to
127/// aid debugging.
128///
129/// It is automatically implemented for types which implement `Taggable` and are `Any`.
130///
131/// When set, the tag will be included in the [Debug] representation of [TaskId]s, which can help
132/// identify tasks in failing Shuttle tests. A task's [Tag] can be set with the
133/// [set_tag_for_current_task](crate::current::set_tag_for_current_task) function. Newly spawned
134/// threads and futures inherit the tag of their parent at spawn time.
135#[deprecated]
136#[allow(deprecated)]
137pub trait Tag: Taggable {
138 /// Return the tag as `Any`, typically so that it can be downcast to a known concrete type
139 fn as_any(&self) -> &dyn Any;
140}
141/// `Taggable` is a marker trait which types implementing `Tag` have to implement.
142/// It exists since we both want to provide a blanket implementation of `as_any`, and have users
143/// opt in to a type being able to be used as a tag. If we did not have this trait, then `Tag`
144/// would be automatically implemented for most types (as most types are `Debug + Any`), which
145/// opens up for accidentally using a type which was not intended to be used as a tag as a tag.
146#[deprecated]
147pub trait Taggable: Debug {}
148
149#[allow(deprecated)]
150impl<T> Tag for T
151where
152 T: Taggable + Any,
153{
154 fn as_any(&self) -> &dyn Any {
155 self
156 }
157}
158
159/// A task signature is an identifier that is intended to be *mostly* stable across executions
160/// and allow for categorization of tasks according to how they were created. It provides two
161/// levels of granularity: static (compile-time) spawn location and dynamic (run-time) context where
162/// that spawn location was reached. The static spawn location and signature are each represented
163/// by a u64 so that the details of how they are computed can be non-breaking changes in the future.
164/// Hashes are all pre-computed for fast checking of equality of signatures at runtime.
165#[derive(Debug, Clone)]
166pub struct TaskSignature {
167 /// The task creation stack is a tuple of (create location, number of tasks created at that location in the parent)
168 task_creation_stack: Vec<(&'static Location<'static>, u32)>,
169 spawn_call_site_hash: u64,
170 parent_signature_hash: u64,
171 signature_hash: u64,
172 child_counters: HashMap<&'static Location<'static>, u32>,
173}
174
175impl TaskSignature {
176 pub fn new_parentless(spawn_call_site: &'static Location<'static>) -> TaskSignature {
177 let mut hasher = DefaultHasher::new();
178 let task_creation_stack = vec![(spawn_call_site, 0)];
179 task_creation_stack.hash(&mut hasher);
180 let signature_hash = hasher.finish();
181 spawn_call_site.hash(&mut hasher);
182
183 Self {
184 task_creation_stack,
185 spawn_call_site_hash: hasher.finish(),
186 parent_signature_hash: 0,
187 signature_hash,
188 child_counters: HashMap::new(),
189 }
190 }
191
192 pub fn new_child(&mut self, spawn_call_site: &'static Location<'static>) -> Self {
193 let mut hasher = DefaultHasher::new();
194 let counter = self
195 .child_counters
196 .entry(spawn_call_site)
197 .and_modify(|c| *c += 1)
198 .or_insert(1);
199 let mut task_creation_stack = self.task_creation_stack.clone();
200 task_creation_stack.push((spawn_call_site, *counter));
201
202 spawn_call_site.hash(&mut hasher);
203 let spawn_call_site_hash = hasher.finish();
204
205 task_creation_stack.hash(&mut hasher);
206
207 Self {
208 task_creation_stack,
209 parent_signature_hash: self.signature_hash,
210 spawn_call_site_hash,
211 signature_hash: hasher.finish(),
212 child_counters: HashMap::new(),
213 }
214 }
215
216 #[track_caller]
217 pub fn new_resource(&mut self, resource_type: ResourceType) -> ResourceSignature {
218 let static_create_location = Location::caller();
219 let counter = self
220 .child_counters
221 .entry(static_create_location)
222 .and_modify(|c| *c += 1)
223 .or_insert(1);
224
225 ResourceSignature::new(resource_type, static_create_location, self.signature_hash, *counter)
226 }
227
228 /// Hash of the static location within the source code where the task was spawned
229 pub fn static_create_location_hash(&self) -> u64 {
230 self.spawn_call_site_hash
231 }
232
233 /// Combined signature of the static location and dynamic context
234 /// context where the task was spawned.
235 pub fn signature_hash(&self) -> u64 {
236 self.signature_hash
237 }
238
239 /// Signature hash of the parent of this task
240 pub fn parent_signature_hash(&self) -> u64 {
241 self.parent_signature_hash
242 }
243}
244
245impl Hash for TaskSignature {
246 fn hash<H: Hasher>(&self, state: &mut H) {
247 self.task_creation_stack.hash(state);
248 }
249}
250
251impl PartialEq for TaskSignature {
252 fn eq(&self, other: &Self) -> bool {
253 self.signature_hash == other.signature_hash
254 }
255}
256
257impl Eq for TaskSignature {}
258
259/// A task's default `tracing` dispatcher, parked while the task is switched out (see
260/// `Execution::park_task_default`).
261#[derive(Debug)]
262pub(crate) struct ParkedDefault(Option<tracing::dispatcher::DefaultGuard>);
263
264impl ParkedDefault {
265 pub(crate) fn new(guard: tracing::dispatcher::DefaultGuard) -> Self {
266 Self(Some(guard))
267 }
268
269 /// Reinstates the task's default dispatcher.
270 pub(crate) fn reinstate(mut self) {
271 drop(self.0.take());
272 }
273}
274
275impl Drop for ParkedDefault {
276 // Only reached when the task's stack is leaked (see `ExecutionState::tear_down`), or the task is
277 // dropped some other way while switched out. Reinstating the task's default lets the guards on
278 // its stack restore their priors in order as the stack unwinds, which leaves the execution's
279 // default in place. A stack that is leaked instead of unwound would leave the task's default
280 // installed for good, so in that case keep the execution's default, and leak the guard along
281 // with the stack.
282 fn drop(&mut self) {
283 if let Some(guard) = self.0.take() {
284 if std::thread::panicking() || ExecutionState::execution_stopped() {
285 std::mem::forget(guard);
286 }
287 }
288 }
289}
290
291/// The slot in which a future task keeps its future, a `RefCell<Option<F>>` in an allocation of its
292/// own (see `Task::from_future`). The loop that polls the future borrows the slot for exactly as
293/// long as it polls the future. So a future task whose slot is not borrowed, and which has not
294/// finished, is parked between two polls, and execution teardown can cancel it there, without
295/// unwinding its stack (see `ExecutionState::tear_down`).
296trait FutureSlot {
297 /// Whether the future is being polled.
298 fn is_being_polled(&self) -> bool;
299}
300
301impl<F> FutureSlot for RefCell<Option<F>> {
302 fn is_being_polled(&self) -> bool {
303 self.try_borrow_mut().is_err()
304 }
305}
306
307/// Poll the future in `slot` where it is.
308fn poll_in_place<F: Future>(slot: &RefCell<Option<F>>, cx: &mut Context<'_>) -> Poll<F::Output> {
309 let mut slot = slot.borrow_mut();
310 let future = slot
311 .as_mut()
312 .expect("a future task drops its future only when it stops polling it");
313 // SAFETY: nothing moves the future out of the slot, whose allocation stays where it is, so the
314 // future stays where it is until it is dropped there.
315 unsafe { Pin::new_unchecked(future) }.poll(cx)
316}
317
318/// A `Task` represents a user-level unit of concurrency. Each task has an `id` that is unique within
319/// the execution, and a `state` reflecting whether the task is runnable (enabled) or not.
320#[derive(Debug)]
321pub struct Task {
322 pub(super) id: TaskId,
323 pub(super) parent_task_id: Option<TaskId>,
324 pub(super) state: TaskState,
325 pub(super) detached: bool,
326 park_state: ParkState,
327
328 // The task's default `tracing` dispatcher while the task is switched out. Declared before
329 // `continuation`, so that it is dropped before the task's stack is (see `ParkedDefault`).
330 pub(super) parked_default: Option<ParkedDefault>,
331
332 // The slot of a future task's future (see `FutureSlot`), until the task finishes. `None` for
333 // threads.
334 future: Option<Weak<dyn FutureSlot>>,
335
336 // `None` only once execution teardown has taken it, and then `yielder` is null once the stack is
337 // gone.
338 pub(super) continuation: Option<Rc<RefCell<PooledContinuation>>>,
339 pub(super) yielder: *const Yielder<ContinuationInput, ContinuationOutput>,
340
341 pub clock: VectorClock,
342
343 waiter: Option<TaskId>,
344
345 // Remember whether the waker was invoked while we were running
346 woken: bool,
347
348 name: Option<String>,
349
350 local_storage: StorageMap,
351
352 // The `Span` which looks like this: step{task=task_id}, or, if step count recording is enabled, like this:
353 // step{task=task_id i=step_count}. Becomes the parent of the spans created by the `Task`.
354 pub step_span: Span,
355
356 // The current `Span` "stack" of the `Task`.
357 // `Span`s are stored such that the `Task`s current `Span` is at `span_stack[0]`, that `Span`s parent (if it exists)
358 // is at `span_stack[1]`, and so on, until `span_stack[span_stack.len()-1]`, which is the "outermost" (left-most when printed)
359 // `Span`. This means that `span_stack[span_stack.len()-1]` will usually be the `Span` saying `execution{i=X}`.
360 // We `pop` it empty when resuming a `Task`, and `push` + `exit` `tracing::Span::current()`
361 // until there is no entered `Span` when we switch out of the `Task`.
362 // There are two things to note:
363 // 1: We have to own the `Span`s (versus storing `Id`s) for the `Span` to not get dropped while the task is switched out.
364 // 2: We have to store the stack of `Span`s in order to return to the correct `Span` once the `Entered<'_>` from an
365 // `instrument`ed future is dropped.
366 pub(super) span_stack: Vec<Span>,
367
368 // Arbitrarily settable tag which is inherited from the parent.
369 #[allow(deprecated)]
370 tag: Option<Arc<dyn Tag>>,
371
372 /// If [`crate::CAPTURE_BACKTRACE`] is set then this will be populated on task block.
373 /// If the test then fails, then each task's backtrace will be printed.
374 pub backtrace: Option<Backtrace>,
375
376 /// The signature of a Task; this is an identifier that is *not* guaranteed to be unique but should be *mostly*
377 /// stable across iterations in a single Shuttle test. Tasks with the same signature are very likely to exhibit
378 /// similar behavior
379 pub signature: TaskSignature,
380}
381
382#[allow(deprecated)]
383impl Task {
384 /// Create a task from a continuation
385 #[allow(clippy::too_many_arguments)]
386 fn new(
387 f: Box<dyn FnOnce() + 'static>,
388 stack_size: usize,
389 id: TaskId,
390 name: Option<String>,
391 clock: VectorClock,
392 parent_span_id: Option<tracing::span::Id>,
393 schedule_len: usize,
394 tag: Option<Arc<dyn Tag>>,
395 parent_task_id: Option<TaskId>,
396 signature: TaskSignature,
397 ) -> Self {
398 #[cfg(all(any(test, feature = "vector-clocks"), not(feature = "bench-no-vector-clocks")))]
399 assert!(id.0 < clock.time.len());
400 let mut continuation = ContinuationPool::acquire(stack_size);
401 continuation.initialize(f);
402 let yielder = continuation.yielder;
403 let continuation = Rc::new(RefCell::new(continuation));
404
405 let step_span =
406 error_span!(parent: parent_span_id.clone(), "step", task = format!("{:?}", id), i = field::Empty);
407 // Note that this is slightly lazy — we are starting storing at the step_span, but could have gotten the
408 // full `Span` stack and stored that. It should be fine, but if any issues arise, then full storing should
409 // be tried.
410 let span_stack = vec![step_span.clone()];
411
412 let mut task = Self {
413 id,
414 parent_task_id,
415 state: TaskState::Runnable,
416 parked_default: None,
417 future: None,
418 continuation: Some(continuation),
419 yielder,
420 clock,
421 waiter: None,
422 woken: false,
423 detached: false,
424 park_state: ParkState::default(),
425 name,
426 step_span,
427 span_stack,
428 local_storage: StorageMap::new(),
429 tag: None,
430 backtrace: None,
431 signature,
432 };
433
434 if let Some(tag) = tag {
435 task.set_tag(tag);
436 }
437
438 // Note: the tests for the task signature in [`crate::tests::basic::task`] depend on tracing the task signature and creation point here
439 error_span!(parent: parent_span_id, "new_task", parent = ?parent_task_id, i = schedule_len).in_scope(
440 || event!(Level::DEBUG, task_id = ?task.id, signature = task.signature.signature_hash(), static_create_location = task.signature.static_create_location_hash(), "created task"),
441 );
442
443 task
444 }
445
446 #[allow(clippy::too_many_arguments)]
447 pub fn from_closure(
448 f: Box<dyn FnOnce() + 'static>,
449 stack_size: usize,
450 id: TaskId,
451 name: Option<String>,
452 clock: VectorClock,
453 parent_span_id: Option<tracing::span::Id>,
454 schedule_len: usize,
455 tag: Option<Arc<dyn Tag>>,
456 parent_task_id: Option<TaskId>,
457 signature: TaskSignature,
458 ) -> Self {
459 Self::new(
460 f,
461 stack_size,
462 id,
463 name,
464 clock,
465 parent_span_id,
466 schedule_len,
467 tag,
468 parent_task_id,
469 signature,
470 )
471 }
472
473 #[allow(clippy::too_many_arguments)]
474 pub fn from_future<F>(
475 future: F,
476 stack_size: usize,
477 id: TaskId,
478 name: Option<String>,
479 clock: VectorClock,
480 parent_span_id: Option<tracing::span::Id>,
481 schedule_len: usize,
482 tag: Option<Arc<dyn Tag>>,
483 parent_task_id: Option<TaskId>,
484 signature: TaskSignature,
485 ) -> Self
486 where
487 F: Future<Output = ()> + 'static,
488 {
489 // The future lives in the slot, where it stays pinned (see `poll_in_place`). The loop below
490 // owns the slot, and the task keeps a weak reference to it (see `FutureSlot`).
491 let slot = Rc::new(RefCell::new(Some(future)));
492 let weak_slot: Weak<dyn FutureSlot> = Rc::downgrade(&slot) as _;
493
494 let mut task = Self::new(
495 Box::new(move || {
496 let waker = ExecutionState::with(|state| state.current_mut().waker());
497 let cx = &mut Context::from_waker(&waker);
498 while poll_in_place(&slot, cx).is_pending() {
499 ExecutionState::with(|state| state.current_mut().sleep_unless_woken());
500 if thread::switch_between_polls() {
501 // Execution teardown cancelled the task (see `ExecutionState::tear_down`).
502 // Drop the future here, on the task's own stack, and then the task's
503 // task-local values, as the task does when its future finishes.
504 *slot.borrow_mut() = None;
505 ExecutionState::drop_task_locals();
506 break;
507 }
508 }
509 }),
510 stack_size,
511 id,
512 name,
513 clock,
514 parent_span_id,
515 schedule_len,
516 tag,
517 parent_task_id,
518 signature,
519 );
520 task.future = Some(weak_slot);
521 task
522 }
523
524 /// Whether the task has not started running (see `ExecutionState::tear_down`).
525 pub(crate) fn never_ran(&self) -> bool {
526 self.continuation.as_ref().is_some_and(|c| c.borrow().never_ran())
527 }
528
529 /// Whether the task is suspended in the middle of its function, and can be resumed.
530 pub(crate) fn suspended(&self) -> bool {
531 self.continuation.as_ref().is_some_and(|c| c.borrow().suspended())
532 }
533
534 /// Whether this is a future task that is parked between two polls of its future (see
535 /// `FutureSlot`).
536 pub(crate) fn parked_between_polls(&self) -> bool {
537 self.future
538 .as_ref()
539 .and_then(Weak::upgrade)
540 .is_some_and(|slot| !slot.is_being_polled())
541 }
542
543 /// Take the task's continuation, for execution teardown to unwind or leak the task's stack (see
544 /// `ExecutionState::tear_down`). The task can switch only while teardown unwinds the stack.
545 pub(crate) fn take_continuation(&mut self) -> Option<Rc<RefCell<PooledContinuation>>> {
546 self.continuation.take()
547 }
548
549 /// The task's stack is gone, once execution teardown has unwound or leaked it: the task cannot
550 /// switch any more.
551 pub(crate) fn lose_stack(&mut self) {
552 self.yielder = std::ptr::null();
553 }
554
555 /// Returns the identifier of this task.
556 pub fn id(&self) -> TaskId {
557 self.id
558 }
559
560 /// Returns the identifier of the task that spawned this task.
561 pub fn parent_task_id(&self) -> Option<TaskId> {
562 self.parent_task_id
563 }
564
565 pub fn runnable(&self) -> bool {
566 self.state == TaskState::Runnable
567 }
568
569 pub fn blocked(&self) -> bool {
570 matches!(self.state, TaskState::Blocked { .. })
571 }
572
573 pub fn can_spuriously_wakeup(&self) -> bool {
574 match self.state {
575 TaskState::Blocked { allow_spurious_wakeups } => allow_spurious_wakeups,
576 _ => false,
577 }
578 }
579
580 pub fn sleeping(&self) -> bool {
581 self.state == TaskState::Sleeping
582 }
583
584 pub fn finished(&self) -> bool {
585 self.state == TaskState::Finished
586 }
587
588 pub fn is_detached(&self) -> bool {
589 self.detached
590 }
591
592 pub fn detach(&mut self) {
593 self.detached = true;
594 }
595
596 /// Wake this task so the Wrapper future can observe the abort flag on its next poll.
597 pub fn abort(&mut self) {
598 if self.finished() {
599 return;
600 }
601 self.wake();
602 }
603
604 pub fn waker(&self) -> Waker {
605 // A task's waker is just the task's id (see `make_waker`), so there's nothing to keep.
606 make_waker(self.id)
607 }
608
609 /// Block the current thread. If `allow_spurious_wakeups` is true, then the scheduler is
610 /// permitted to spuriously wake up the thread (though it will still not count as a live thread
611 /// for deadlock detection purposes for as long as it remains blocked).
612 pub fn block(&mut self, allow_spurious_wakeups: bool) {
613 self.backtrace = if backtrace_enabled() {
614 Some(Backtrace::force_capture())
615 } else {
616 None
617 };
618
619 assert!(self.state != TaskState::Finished);
620 self.state = TaskState::Blocked { allow_spurious_wakeups };
621 }
622
623 pub fn sleep(&mut self) {
624 assert!(self.state != TaskState::Finished);
625 self.state = TaskState::Sleeping;
626 }
627
628 pub fn unblock(&mut self) {
629 // Note we don't assert the task is blocked here. For example, a task invoking its own waker
630 // will not be blocked when this is called.
631 assert!(self.state != TaskState::Finished);
632 self.state = TaskState::Runnable;
633
634 // When a task gets unblocked, it's definitely no longer blocked in a call to `park`. This
635 // is necessary to do here because a parked task could be spuriously woken up outside of the
636 // `unpark` path. If it later becomes blocked by something else, we don't want a later
637 // `unpark` to be able to unblock the task.
638 self.park_state.blocked_in_park = false;
639 }
640
641 pub fn finish(&mut self) {
642 assert!(self.state != TaskState::Finished);
643 self.state = TaskState::Finished;
644 // Only an unfinished task is ever torn down (see `FutureSlot`).
645 self.future = None;
646 }
647
648 /// Make the task runnable while execution teardown drops something of the task's on the
649 /// executor's stack, as the task (see `ExecutionState::tear_down`): its task-local values once
650 /// its stack is gone, or a static, for which the main thread stands in. Then what destructors do
651 /// to the task (block it, park it) works as in a running task, even though the task may have
652 /// finished. Returns the task's state, for `stop_standing_in`.
653 pub(crate) fn stand_in(&mut self) -> TaskState {
654 self.park_state.blocked_in_park = false;
655 std::mem::replace(&mut self.state, TaskState::Runnable)
656 }
657
658 /// Undo `stand_in`.
659 pub(crate) fn stop_standing_in(&mut self, state: TaskState) {
660 self.state = state;
661 }
662
663 /// Potentially put this task to sleep after it was polled by the executor, unless someone has
664 /// called its waker first.
665 ///
666 /// A synchronous Task should never call this, because we want threads to be enabled-by-default
667 /// to avoid bugs where Shuttle incorrectly omits a potential execution.
668 pub fn sleep_unless_woken(&mut self) {
669 let was_woken = std::mem::replace(&mut self.woken, false);
670 if !was_woken {
671 self.sleep();
672 }
673 }
674
675 /// Remember that our waker has been called, and so we should not block the next time the
676 /// executor tries to put us to sleep.
677 pub(super) fn wake(&mut self) {
678 self.woken = true;
679 if self.state == TaskState::Sleeping {
680 self.unblock();
681 }
682 }
683
684 /// Register a waiter for this thread to terminate. Returns a boolean indicating whether the
685 /// waiter should block or not. If false, this task has already finished, and so the waiter need
686 /// not block.
687 pub fn set_waiter(&mut self, waiter: TaskId) -> bool {
688 assert!(
689 self.waiter.is_none() || self.waiter == Some(waiter),
690 "Task cannot have more than one waiter"
691 );
692 if self.finished() {
693 false
694 } else {
695 self.waiter = Some(waiter);
696 true
697 }
698 }
699
700 pub fn take_waiter(&mut self) -> Option<TaskId> {
701 self.waiter.take()
702 }
703
704 pub fn name(&self) -> Option<String> {
705 self.name.clone()
706 }
707
708 /// The task's name, or `task-N` if it has none.
709 pub fn display_name(&self) -> String {
710 self.name().unwrap_or_else(|| format!("task-{}", self.id.0))
711 }
712
713 /// Retrieve a reference to the given thread-local storage slot.
714 ///
715 /// Returns Some(Err(_)) if the slot has already been destructed. Returns None if the slot has
716 /// not yet been initialized.
717 pub fn local<T: 'static>(&self, key: &'static LocalKey<T>) -> Option<Result<&T, AlreadyDestructedError>> {
718 self.local_storage.get(key.into())
719 }
720
721 /// Initialize the given thread-local storage slot with a new value.
722 ///
723 /// Panics if the slot has already been initialized.
724 pub fn init_local<T: 'static>(&mut self, key: &'static LocalKey<T>, value: T) {
725 self.local_storage.init(key.into(), value)
726 }
727
728 /// Whether some thread-local storage slot is still initialized.
729 pub(crate) fn has_locals(&self) -> bool {
730 !self.local_storage.is_empty()
731 }
732
733 /// Whether the task holds something whose destructor may run user code: a span that a `tracing`
734 /// subscriber sees close, or a tag (see `ExecutionState::tear_down`).
735 pub(crate) fn has_leftovers(&self) -> bool {
736 self.tag.is_some() || !self.step_span.is_disabled() || self.span_stack.iter().any(|span| !span.is_disabled())
737 }
738
739 /// Take what `has_leftovers` looks for, to be dropped.
740 pub(crate) fn take_leftovers(&mut self) -> impl Any {
741 let step_span = std::mem::replace(&mut self.step_span, Span::none());
742 (step_span, std::mem::take(&mut self.span_stack), self.tag.take())
743 }
744
745 /// Return ownership of the next still-initialized thread-local storage slot, to be used when
746 /// running thread-local storage destructors.
747 ///
748 /// TLS destructors are a little tricky:
749 /// 1. Their code can perform synchronization operations (and so require Shuttle to call back
750 /// into ExecutionState), so we can't drop them from within an ExecutionState borrow. Instead
751 /// we move the contents of a slot to the caller to be dropped outside the borrow.
752 /// 2. It's valid for destructors to read other TLS slots, although destructor order is
753 /// undefined. This also means it's valid for a destructor to *initialize* another TLS slot.
754 /// To make this work, we run the destructors incrementally, so one destructor can initialize
755 /// another slot that just gets added via `init_local` like normal, and then will be
756 /// available to be popped on a future call to `pop_local`. To prevent an infinite loop, we
757 /// forbid *reinitializing* a TLS slot whose destructor has already run, or is currently
758 /// being run.
759 pub fn pop_local(&mut self) -> Option<Box<dyn Any>> {
760 self.local_storage.pop()
761 }
762
763 /// Park the task if its park token is unavailable. If the task blocks, then it will be woken up
764 /// when the token becomes available or spuriously without consuming the token (see the
765 /// documentation for [`std::thread::park`], which says that "it may also return spuriously,
766 /// without consuming the token"). Returns true if the execution should switch to a different
767 /// task (e.g., if the token was unavailable).
768 pub fn park(&mut self) -> bool {
769 assert!(
770 !self.park_state.blocked_in_park,
771 "task cannot park while already parked"
772 );
773 assert!(!self.blocked(), "task cannot park while blocked by something else");
774
775 if self.park_state.token_available {
776 self.park_state.token_available = false;
777 false
778 } else {
779 self.park_state.blocked_in_park = true;
780 self.block(true);
781 true
782 }
783 }
784
785 /// Make the task's park token available, and unblock the task if it was parked.
786 pub fn unpark(&mut self) {
787 if self.park_state.blocked_in_park {
788 assert!(
789 self.blocked() && self.can_spuriously_wakeup(),
790 "parked tasks should be blocked"
791 );
792 assert!(
793 !self.park_state.token_available,
794 "token shouldn't be available for parked task"
795 );
796
797 self.unblock();
798 } else {
799 // If the thread isn't currently blocked in `park`, then make the token available. If
800 // the token already is available, then this does nothing.
801 self.park_state.token_available = true;
802 }
803 }
804
805 pub fn get_tag(&self) -> Option<Arc<dyn Tag>> {
806 self.tag.clone()
807 }
808
809 /// Sets the `tag` field of the current task.
810 /// Returns the `tag` which was there previously.
811 pub fn set_tag(&mut self, tag: Arc<dyn Tag>) -> Option<Arc<dyn Tag>> {
812 TASK_ID_TO_TAGS.with(|cell| cell.borrow_mut().insert(self.id(), tag.clone()));
813 self.tag.replace(tag)
814 }
815
816 pub fn format_for_deadlock(&self) -> String {
817 use crate::backtrace_enabled;
818 format!(
819 "{} (task {:?}{}{}){}",
820 self.name().unwrap_or_else(|| "<unknown>".to_string()),
821 self.id(),
822 if self.detached { ", detached" } else { "" },
823 if self.sleeping() { ", pending future" } else { "" },
824 match (backtrace_enabled(), &self.backtrace) {
825 // `Display` prints the numbered `N: function` / `at file:line:col` layout that panics
826 // use under `RUST_BACKTRACE=1`, and ends each frame with a newline.
827 (true, Some(backtrace)) => format!("\nBacktrace:\n{backtrace}"),
828 (true, None) => "\nBacktrace: <not captured>\n".into(),
829 (false, _) => "".into(),
830 }
831 )
832 }
833}
834
835#[derive(PartialEq, Eq, Clone, Copy, Debug)]
836pub enum TaskState {
837 /// Available to be scheduled
838 Runnable,
839 /// Blocked in a synchronization operation
840 Blocked { allow_spurious_wakeups: bool },
841 /// A `Future` that returned `Pending` is waiting to be woken up
842 Sleeping,
843 /// Task has finished
844 Finished,
845}
846
847#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
848pub struct ParkState {
849 /// Whether the task's park token is currently available. If it's available, then the next time
850 /// the task calls `park`, the token will be atomically consumed and the task will continue
851 /// executing. If it's not available, then the task will block until either another task makes
852 /// it available with `unpark`, or a spurious wakeup occurs.
853 token_available: bool,
854
855 /// Whether the task is currently blocked in a call to `park`.
856 /// Invariant: `!(token_available && blocked_in_park)`. If the token is available, then the task
857 /// shouldn't be blocked in a call to `park`---the task should either have been woken up when
858 /// the token became available, or never have blocked in the first place if the token was
859 /// available before the call to `park`.
860 blocked_in_park: bool,
861}
862
863/// A `TaskId` is a unique identifier for a task. `TaskId`s are never reused within a single
864/// execution.
865#[derive(PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
866pub struct TaskId(pub(super) usize);
867
868impl Debug for TaskId {
869 // If the `TaskName` label is set, use that when generating the Debug string
870 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
871 if let Some(name) = get_name_for_task(*self) {
872 f.write_str(&format!("{:?}({})", name, self.0))
873 } else {
874 f.debug_tuple("TaskId").field(&self.0).finish()
875 }
876 }
877}
878
879impl std::fmt::Display for TaskId {
880 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
881 std::fmt::Display::fmt(&self.0, f)
882 }
883}
884
885impl From<usize> for TaskId {
886 fn from(id: usize) -> Self {
887 TaskId(id)
888 }
889}
890
891impl From<TaskId> for usize {
892 fn from(tid: TaskId) -> usize {
893 tid.0
894 }
895}
896
897/// A `TaskSet` is a set of `TaskId`s but implemented efficiently as a BitVec
898#[derive(PartialEq, Eq)]
899pub struct TaskSet {
900 tasks: BitVec,
901}
902
903impl TaskSet {
904 pub const fn new() -> Self {
905 Self { tasks: BitVec::EMPTY }
906 }
907
908 pub fn contains(&self, tid: TaskId) -> bool {
909 // Return false if tid is outside the TaskSet
910 (tid.0 < self.tasks.len()) && self.tasks[tid.0]
911 }
912
913 pub fn is_empty(&self) -> bool {
914 self.tasks.iter().all(|b| !*b)
915 }
916
917 /// Add a task to the set. If the set did not have this value present, `true` is returned. If
918 /// the set did have this value present, `false` is returned.
919 pub fn insert(&mut self, tid: TaskId) -> bool {
920 if tid.0 >= self.tasks.len() {
921 self.tasks.resize(DEFAULT_INLINE_TASKS.max(1 + tid.0), false);
922 }
923 !std::mem::replace(&mut *self.tasks.get_mut(tid.0).unwrap(), true)
924 }
925
926 /// Removes a value from the set. Returns whether the value was present in the set.
927 pub fn remove(&mut self, tid: TaskId) -> bool {
928 if tid.0 >= self.tasks.len() {
929 return false;
930 }
931 std::mem::replace(&mut self.tasks.get_mut(tid.0).unwrap(), false)
932 }
933
934 pub fn iter(&self) -> impl Iterator<Item = TaskId> + '_ {
935 self.tasks
936 .iter()
937 .enumerate()
938 .filter(|(_, b)| **b)
939 .map(|(i, _)| TaskId(i))
940 }
941}
942
943impl Debug for TaskSet {
944 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
945 write!(f, "TaskSet {{ ")?;
946 for (i, t) in self.iter().enumerate() {
947 if i > 0 {
948 write!(f, ", ")?;
949 }
950 write!(f, "{t:?}")?;
951 }
952 write!(f, " }}")
953 }
954}
955
956impl<T: 'static> From<&'static LocalKey<T>> for StorageKey {
957 fn from(key: &'static LocalKey<T>) -> Self {
958 Self(key as *const _ as usize, 0x1)
959 }
960}