Skip to main content

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}