Skip to main content

shuttle_engine/runtime/
execution.rs

1use crate::runtime::failure::{init_panic_hook, persist_failure};
2use crate::runtime::storage::{StorageKey, StorageMap};
3use crate::runtime::task::clock::VectorClock;
4use crate::runtime::task::labels::Labels;
5use crate::runtime::task::{ChildLabelFn, Task, TaskId, TaskName, TaskSignature, DEFAULT_INLINE_TASKS};
6use crate::runtime::thread;
7use crate::runtime::thread::continuation::PooledContinuation;
8use crate::scheduler::{Schedule, Scheduler};
9use crate::sync_types::{ResourceSignature, ResourceType};
10use crate::thread_support::thread_fn;
11use crate::{backtrace_enabled, Config, MaxSteps, UNGRACEFUL_SHUTDOWN_CONFIG};
12use scoped_tls::scoped_thread_local;
13use smallvec::SmallVec;
14use std::any::Any;
15use std::cell::RefCell;
16use std::collections::HashMap;
17use std::fmt::Debug;
18use std::future::Future;
19use std::panic::{self, Location};
20use std::rc::Rc;
21use std::sync::Arc;
22use tracing::{trace, Span};
23
24#[allow(deprecated)]
25use super::task::Tag;
26
27// We use this scoped TLS to smuggle the ExecutionState, which is not 'static, across tasks that
28// need access to it (to spawn new tasks, interrogate task status, etc).
29scoped_thread_local! {
30    static EXECUTION_STATE: RefCell<ExecutionState>
31}
32
33// The reason this is separated out from `ExecutionState` is to ensure that we're always able to persist the schedule.
34// If we don't do this, then we may panic while borrowing `ExecutionState`, and then not be able to emit the schedule.
35// If we then panic again while trying to handle the panic, such that the panic becomes an abort, we will never log
36// the schedule.
37//
38// It is expected that if the `ExecutionState` exists, then this will exist, and any usage of this happens through the
39// `ExecutionState`, or at a point where it is known that the `ExecutionState` must exist (eg. when serializing on a panic).
40thread_local! {
41    static CURRENT_SCHEDULE: CurrentSchedule = CurrentSchedule::default();
42}
43
44#[derive(Debug, Default)]
45pub struct CurrentSchedule {
46    current_schedule: RefCell<Schedule>,
47}
48
49impl CurrentSchedule {
50    fn init(schedule: Schedule) {
51        CURRENT_SCHEDULE.with(|cs| *cs.current_schedule.borrow_mut() = schedule)
52    }
53
54    /// Add the given task ID as the next step of the schedule.
55    fn push_task(tid: TaskId) {
56        CURRENT_SCHEDULE.with(|cs| cs.current_schedule.borrow_mut().push_task(tid))
57    }
58
59    /// Add a choice of a random u64 value as the next step of the schedule
60    fn push_random() {
61        CURRENT_SCHEDULE.with(|cs| cs.current_schedule.borrow_mut().push_random())
62    }
63
64    /// Return the number of steps in the schedule
65    pub fn len() -> usize {
66        CURRENT_SCHEDULE.with(|cs| (*cs.current_schedule.borrow()).len())
67    }
68
69    /// Returns a clone of the inner schedule
70    pub fn get_schedule() -> Schedule {
71        CURRENT_SCHEDULE.with(|cs| (*cs.current_schedule.borrow()).clone())
72    }
73}
74
75thread_local! {
76    #[allow(clippy::complexity)]
77    #[allow(deprecated)]
78    pub static TASK_ID_TO_TAGS: RefCell<HashMap<TaskId, Arc<dyn Tag>>> = RefCell::new(HashMap::new());
79}
80
81thread_local! {
82    pub static LABELS: RefCell<HashMap<TaskId, Labels>> = RefCell::new(HashMap::new());
83}
84
85/// An `Execution` encapsulates a single run of a function under test against a chosen scheduler.
86/// Its only useful method is `Execution::run`, which executes the function to completion.
87///
88/// The key thing that an `Execution` manages is the `ExecutionState`, which contains all the
89/// mutable state a test's tasks might need access to during execution (to block/unblock tasks,
90/// spawn new tasks, etc). The `Execution` makes this state available through the `EXECUTION_STATE`
91/// static variable, but clients get access to it by calling `ExecutionState::with`.
92pub struct Execution {
93    scheduler: Rc<RefCell<dyn Scheduler>>,
94    initial_schedule: Schedule,
95}
96
97impl std::fmt::Debug for Execution {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("Execution").finish_non_exhaustive()
100    }
101}
102
103impl Execution {
104    /// Construct a new execution that will use the given scheduler. The execution should then be
105    /// invoked via its `run` method, which takes as input the closure for task 0.
106    pub fn new(scheduler: Rc<RefCell<dyn Scheduler>>, initial_schedule: Schedule) -> Self {
107        Self {
108            scheduler,
109            initial_schedule,
110        }
111    }
112}
113
114#[derive(Debug)]
115enum StepError {
116    // Contains the panic payload of the task that failed.
117    TaskFailure(Box<dyn Any + Send>),
118    // The scheduler didn't make a decision. Indicates a scheduler error.
119    SchedulingError,
120    // Scheduling deetected a deadlock.
121    Deadlock,
122    // We exceeded the step bound.
123    StepBoundExceeded,
124    // Task panic and `config.immediately_return_on_panic` is set to `true`.
125    TaskPanicEarlyReturn,
126}
127
128impl StepError {
129    fn persist_failure(&self, config: &Config) {
130        if let StepError::StepBoundExceeded = self {
131            if let MaxSteps::ContinueAfter(_) = config.max_steps {
132                return;
133            }
134        }
135        persist_failure(config);
136    }
137}
138
139impl Execution {
140    /// Run a function to be tested, taking control of scheduling it and any tasks it might spawn.
141    /// This function runs until `f` and all tasks spawned by `f` have terminated, or until the
142    /// scheduler returns `None`, indicating the execution should not be explored any further.
143    pub fn run<F>(mut self, config: &Config, f: F, caller: &'static Location<'static>)
144    where
145        F: FnOnce() + Send + 'static,
146    {
147        let state = RefCell::new(ExecutionState::new(config.clone(), Rc::clone(&self.scheduler)));
148
149        init_panic_hook(config.clone());
150        CurrentSchedule::init(self.initial_schedule.clone());
151        UNGRACEFUL_SHUTDOWN_CONFIG.set(config.ungraceful_shutdown_config);
152
153        EXECUTION_STATE.set(&state, move || {
154            // Spawn `f` as the first task
155            ExecutionState::spawn_main_thread(
156                Box::new(move || thread_fn(f, true, Default::default())),
157                config.stack_size,
158                caller,
159            );
160
161                // Run the test to completion
162                match self.run_to_completion(UNGRACEFUL_SHUTDOWN_CONFIG.get().immediately_return_on_panic) {
163                    Ok(()) => {},
164                    Err(e) => {
165                        e.persist_failure(config);
166
167                        match e {
168                            StepError::TaskFailure(payload) => {
169                                eprintln!("test panicked in task '{}'", ExecutionState::failing_task());
170
171                                panic::resume_unwind(payload);
172                            }
173                            StepError::Deadlock => {
174                                let blocked_tasks = ExecutionState::with(|state|
175                                    state
176                                    .tasks
177                                    .iter()
178                                    .filter(|t| !t.finished())
179                                    .map(|t| t.format_for_deadlock())
180                                    .collect::<Vec<_>>());
181
182                                // Collecting backtraces is expensive, so we only want to do it if the user opts in to collecting them.
183                                if !backtrace_enabled() {
184                                    eprintln!("Test deadlocked, and {} is not set. If either of those are set then the backtrace of each task will be collected and printed as part of the panic message.", crate::CAPTURE_BACKTRACE)
185                                }
186
187                                panic!("deadlock! blocked tasks: [{}]", blocked_tasks.join(", "));
188                            }
189                            StepError::SchedulingError => panic!("no task was scheduled\nThis indicates an issue with the scheduler."),
190                            StepError::StepBoundExceeded => {
191                                if let MaxSteps::FailAfter(max_steps) = config.max_steps {
192                                    panic!("exceeded max_steps bound {max_steps}. this might be caused by an unfair schedule (e.g., a spin loop)?");
193                                }
194                            }
195                            StepError::TaskPanicEarlyReturn => panic::resume_unwind(Box::new("Task panicked, and early return is enabled.")),
196                        }
197                    }}
198
199
200                // Cleanup the state before it goes out of `EXECUTION_STATE` scope
201                ExecutionState::cleanup();
202            });
203    }
204
205    fn enter_task_span() {
206        // Enter the Task's span
207        // (Note that if any issues arise with spans and tracing, then
208        // 1) calling `exit` until `None` before entering the `Task`s `Span`,
209        // 2) storing the entirety of the `span_stack` when creating the `Task`, and
210        // 3) storing `top_level_span` as a stack
211        // should be tried.)
212        ExecutionState::with(|state| {
213            tracing::dispatcher::get_default(|subscriber| {
214                if let Some(span_id) = tracing::Span::current().id().as_ref() {
215                    subscriber.exit(span_id);
216                }
217
218                // The `span_stack` stores `Span`s such that the top of the stack is the outermost `Span`,
219                // meaning that parents (left-most when printed) are entered first.
220                while let Some(span) = state.current_mut().span_stack.pop() {
221                    if let Some(span_id) = span.id().as_ref() {
222                        subscriber.enter(span_id)
223                    }
224                }
225
226                if state.config.record_steps_in_span {
227                    state.current().step_span.record("i", CurrentSchedule::len());
228                }
229            });
230        });
231    }
232
233    fn exit_task_span() {
234        // Leave the Task's span and store the exited `Span` stack in order to restore it the next time the Task is run
235        ExecutionState::with(|state| {
236            tracing::dispatcher::get_default(|subscriber| {
237                debug_assert!(state.current().span_stack.is_empty());
238                while let Some(span_id) = tracing::Span::current().id().as_ref() {
239                    state.current_mut().span_stack.push(tracing::Span::current().clone());
240                    subscriber.exit(span_id);
241                }
242
243                if let Some(span_id) = state.top_level_span.id().as_ref() {
244                    subscriber.enter(span_id)
245                }
246            });
247        });
248    }
249
250    /// Run the execution to completion.
251    #[inline]
252    fn run_to_completion(&mut self, immediately_return_on_panic: bool) -> Result<(), StepError> {
253        loop {
254            let next_step: Option<Rc<RefCell<PooledContinuation>>> = ExecutionState::with(|state| {
255                state.schedule()?;
256                state.advance_to_next_task();
257
258                match state.current_task {
259                    ScheduledTask::Some(tid) => {
260                        let task = state.get(tid);
261                        Ok(Some(task.continuation.clone()))
262                    }
263                    ScheduledTask::Finished => {
264                        // The scheduler decided we're finished, so there are either no runnable tasks,
265                        // or all runnable tasks are detached and there are no unfinished attached
266                        // tasks. Therefore, it's a deadlock if there are unfinished attached tasks.
267                        if state.tasks.iter().any(|t| !t.finished() && !t.detached) {
268                            Err(StepError::Deadlock)
269                        } else {
270                            Ok(None)
271                        }
272                    }
273                    ScheduledTask::Stopped => Ok(None),
274                    ScheduledTask::None => Err(StepError::SchedulingError),
275                }
276            })?;
277
278            // Run a single step of the chosen task.
279            let ret = match next_step {
280                Some(continuation) => {
281                    Execution::enter_task_span();
282
283                    let result = panic::catch_unwind(panic::AssertUnwindSafe(|| continuation.borrow_mut().resume()));
284
285                    Execution::exit_task_span();
286
287                    result
288                }
289                None => return Ok(()),
290            };
291
292            match ret {
293                // Task finished
294                Ok(true) => {
295                    crate::annotations::record_task_terminated();
296                    ExecutionState::with(|state| state.finish_current_task());
297                }
298                // Task yielded
299                Ok(false) => {
300                    // We may have `switch`ed out of the task before we finished unwinding the stack (ie. a `drop` handler calls `switch`).
301                    // If `immediately_return_on_panic` is set, we will then return. If we don't do this, then we run the risk of panicking
302                    // again in some other task, which would result in the test aborting.
303                    if immediately_return_on_panic && std::thread::panicking() {
304                        ExecutionState::with(|state| state.current_task = ScheduledTask::Stopped);
305                        return Err(StepError::TaskPanicEarlyReturn);
306                    }
307                }
308                // Task failed
309                Err(e) => return Err(StepError::TaskFailure(e)),
310            }
311        }
312    }
313}
314
315/// `ExecutionState` contains the portion of a single execution's state that needs to be reachable
316/// from within a task's execution. It tracks which tasks exist and their states, as well as which
317/// tasks are pending spawn.
318pub struct ExecutionState {
319    pub config: Config,
320    // invariant: tasks are never removed from this list
321    tasks: SmallVec<[Task; DEFAULT_INLINE_TASKS]>,
322    // invariant: if this transitions to Stopped or Finished, it can never change again
323    current_task: ScheduledTask,
324    // the task the scheduler has chosen to run next
325    next_task: ScheduledTask,
326    // whether the current task has asked to yield
327    has_yielded: bool,
328    // the number of scheduling decisions made so far
329    context_switches: usize,
330    // the schedule length last time `reset_stop_bound()` was called
331    pub steps_reset_at: usize,
332
333    // static values for the current execution
334    storage: StorageMap,
335
336    scheduler: Rc<RefCell<dyn Scheduler>>,
337
338    in_cleanup: bool,
339
340    #[cfg(debug_assertions)]
341    has_cleaned_up: bool,
342
343    // The `Span` which the `ExecutionState` was created under. Will be the parent of all `Task` `Span`s
344    pub top_level_span: Span,
345
346    // Persistent Vec used as a bump allocator for references to runnable tasks to avoid slow allocation
347    // on each scheduling decision. Should not be used outside of the `schedule` function
348    runnable_tasks: Vec<*const Task>,
349
350    // Ids of all tasks that have not yet finished, kept sorted in ascending order.
351    //
352    // `tasks` never shrinks, so it accumulates every task ever created by the execution. Scanning it
353    // on every scheduling decision therefore costs O(tasks ever created), even though only the
354    // unfinished ones can ever be scheduled. This set lets `schedule` iterate just the live tasks.
355    //
356    // invariant: contains exactly the ids of the tasks in `tasks` that are not `Finished`, in
357    // ascending order. Maintained by pushing on task creation (ids are handed out sequentially, so
358    // pushing keeps this sorted) and removing in `finish_current_task`. This is sound because
359    // `Finished` is a terminal state: it is only ever set by `Task::finish`, and `block`, `sleep`,
360    // and `unblock` all assert that they are never applied to a finished task.
361    live_tasks: Vec<TaskId>,
362}
363
364impl std::fmt::Debug for ExecutionState {
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366        f.debug_struct("ExecutionState").finish_non_exhaustive()
367    }
368}
369
370#[derive(Debug, PartialEq, Eq, Clone, Copy)]
371enum ScheduledTask {
372    None,         // no task has ever been scheduled
373    Some(TaskId), // this task is running
374    Stopped,      // the scheduler asked us to stop running
375    Finished,     // all tasks have finished running
376}
377
378impl ScheduledTask {
379    fn id(&self) -> Option<TaskId> {
380        match self {
381            ScheduledTask::Some(tid) => Some(*tid),
382            _ => None,
383        }
384    }
385
386    fn take(&mut self) -> Self {
387        std::mem::replace(self, ScheduledTask::None)
388    }
389}
390
391/// Error type for when an `ExecutionState::with` fails
392#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
393pub enum ExecutionStateBorrowError {
394    /// `ExecutionState` is currently not set
395    NotSet,
396    /// We are trying to borrow `ExecutionState` while it is already borrowed
397    AlreadyBorrowed,
398}
399
400impl ExecutionState {
401    fn new(config: Config, scheduler: Rc<RefCell<dyn Scheduler>>) -> Self {
402        Self {
403            config,
404            tasks: SmallVec::new(),
405            current_task: ScheduledTask::None,
406            next_task: ScheduledTask::None,
407            has_yielded: false,
408            context_switches: 0,
409            steps_reset_at: 0,
410            storage: StorageMap::new(),
411            scheduler,
412            in_cleanup: false,
413            #[cfg(debug_assertions)]
414            has_cleaned_up: false,
415            top_level_span: tracing::Span::current(),
416            runnable_tasks: Vec::with_capacity(DEFAULT_INLINE_TASKS),
417            live_tasks: Vec::with_capacity(DEFAULT_INLINE_TASKS),
418        }
419    }
420
421    /// Invoke a closure with access to the current execution state. Library code uses this to gain
422    /// access to the state of the execution to influence scheduling (e.g. to register a task as
423    /// blocked).
424    #[inline]
425    #[track_caller]
426    pub fn with<F, T>(f: F) -> T
427    where
428        F: FnOnce(&mut ExecutionState) -> T,
429    {
430        Self::try_with(f).unwrap_or_else(|e| {
431            eprintln!("`ExecutionState::try_with` failed with error: {e:?}");
432            eprintln!(
433                "Backtrace for `with`: {:#?}",
434                std::backtrace::Backtrace::force_capture()
435            );
436            match e {
437                ExecutionStateBorrowError::AlreadyBorrowed => panic!("`ExecutionState::with` panicked because `ExecutionState` is already borrowed."),
438                ExecutionStateBorrowError::NotSet => panic!("`ExecutionState::with` panicked because `ExecutionState` is not set. Are you accessing a Shuttle primitive outside of a Shuttle test?"),
439            }
440        })
441    }
442
443    /// Like `with`, but returns None instead of panicking if there is no current ExecutionState or
444    /// if the current ExecutionState is already borrowed.
445    #[inline]
446    #[track_caller]
447    pub fn try_with<F, T>(f: F) -> Result<T, ExecutionStateBorrowError>
448    where
449        F: FnOnce(&mut ExecutionState) -> T,
450    {
451        trace!(
452            "ExecutionState::try_with called from {:?}",
453            std::panic::Location::caller()
454        );
455        if EXECUTION_STATE.is_set() {
456            EXECUTION_STATE.with(|cell| {
457                if let Ok(mut state) = cell.try_borrow_mut() {
458                    Ok(f(&mut state))
459                } else {
460                    Err(ExecutionStateBorrowError::AlreadyBorrowed)
461                }
462            })
463        } else {
464            Err(ExecutionStateBorrowError::NotSet)
465        }
466    }
467
468    /// A shortcut to get the current task ID
469    pub fn me() -> TaskId {
470        Self::with(|s| s.current().id())
471    }
472
473    /// If there is only one attached, unfinished task and there is at least one detached, unfinished task
474    /// then exiting the attached task will cause the whole execution to exit. As a result, the unfinished
475    /// detached tasks are truncated -- their remaining events will not be executed because the program itself
476    /// has exited. This is relevant because it means that *exiting* a task can be a visible operation
477    /// in that it affects which events are executed.
478    pub fn exit_current_truncates_execution(&self) -> bool {
479        // Strictly speaking, this is only true if there are other runnable detached tasks, but always making the main thread
480        // exit a scheduling point is simpler conceptually
481        if self.current().id() == TaskId::from(0) {
482            return true;
483        }
484
485        // If the current task is detached, then it definitely doesn't truncate the execution
486        if self.current().is_detached() {
487            return false;
488        }
489
490        let mut single_unfinished_attached = false;
491        let mut has_unfinished_detached = false;
492        for t in self.tasks.iter() {
493            let unfinished_attached = !t.finished() && !t.detached;
494            if single_unfinished_attached && unfinished_attached {
495                // there are more than one unfinished attached tasks, so one exiting won't truncate
496                return false;
497            }
498
499            single_unfinished_attached |= unfinished_attached;
500            has_unfinished_detached |= !t.finished() && t.detached;
501        }
502        has_unfinished_detached && single_unfinished_attached
503    }
504
505    fn set_labels_for_new_task(state: &ExecutionState, task_id: TaskId, name: Option<String>) {
506        LABELS.with(|cell| {
507            let mut map = cell.borrow_mut();
508
509            // If parent has labels, inherit them
510            if let Some(parent_task_id) = state.try_current().map(|t| t.id()) {
511                let parent_map = map.get(&parent_task_id);
512                if let Some(parent_map) = parent_map {
513                    let mut child_map = parent_map.clone();
514
515                    // If the parent has a `ChildLabelFn` set, use that to update the child's Labels
516                    if let Some(gen) = parent_map.get::<ChildLabelFn>() {
517                        (gen.0)(task_id, &mut child_map);
518                    }
519
520                    map.insert(task_id, child_map);
521                }
522            }
523
524            // Add any name assigned to the task to its set of Labels
525            if let Some(name) = name {
526                let m = map.entry(task_id).or_default();
527                m.insert(TaskName::from(name));
528            }
529        });
530    }
531
532    // Note: `spawn_thread`, `spawn_main_thread`, and `spawn_future` share some similar logic.
533    // Changes to one of these functions likely need to be propagated to the other two as well.
534    pub fn spawn_main_thread(
535        f: Box<dyn FnOnce() + 'static>,
536        stack_size: usize,
537        caller: &'static Location<'static>,
538    ) -> TaskId {
539        let name = "main-thread".to_string();
540        let mut clock = VectorClock::new();
541
542        let task_id = Self::with(|state| {
543            let parent_span_id = state.top_level_span.id();
544            let task_id = TaskId(state.tasks.len());
545            let tag = state.get_tag_or_default_for_current_task();
546
547            Self::set_labels_for_new_task(state, task_id, Some(name.clone()));
548
549            clock.extend(task_id); // and extend it with an entry for the new thread
550
551            let schedule_len = CurrentSchedule::len();
552
553            let task = Task::from_closure(
554                f,
555                stack_size,
556                task_id,
557                Some(name),
558                clock,
559                parent_span_id,
560                schedule_len,
561                tag,
562                None,
563                TaskSignature::new_parentless(caller),
564            );
565            state.add_task(task);
566
567            task_id
568        });
569        crate::annotations::record_task_created(task_id, false);
570        task_id
571    }
572
573    // Note: `spawn_thread`, `spawn_main_thread`, and `spawn_future` share some similar logic.
574    // Changes to one of these functions likely need to be propagated to the other two as well.
575    /// Spawn a new task for a future. This doesn't create a yield point; the caller should do that
576    /// if it wants to give the new task a chance to run immediately.
577    pub fn spawn_future<F>(
578        future: F,
579        stack_size: usize,
580        name: Option<String>,
581        caller: &'static Location<'static>,
582    ) -> TaskId
583    where
584        F: Future<Output = ()> + 'static,
585    {
586        thread::switch();
587        let task_id = Self::with(|state| {
588            let schedule_len = CurrentSchedule::len();
589            let parent_span_id = state.top_level_span.id();
590
591            let task_id = TaskId(state.tasks.len());
592            let tag = state.get_tag_or_default_for_current_task();
593
594            Self::set_labels_for_new_task(state, task_id, name.clone());
595
596            let clock = state.increment_clock_mut(); // Increment the parent's clock
597            clock.extend(task_id); // and extend it with an entry for the new task
598
599            let task = Task::from_future(
600                future,
601                stack_size,
602                task_id,
603                name,
604                clock.clone(),
605                parent_span_id,
606                schedule_len,
607                tag,
608                Some(state.current().id()),
609                state.current_mut().signature.new_child(caller),
610            );
611
612            state.add_task(task);
613
614            task_id
615        });
616        crate::annotations::record_task_created(task_id, true);
617        task_id
618    }
619
620    // Note: `spawn_thread`, `spawn_main_thread`, and `spawn_future` share some similar logic.
621    // Changes to one of these functions likely need to be propagated to the other two as well.
622    pub fn spawn_thread(
623        f: Box<dyn FnOnce() + 'static>,
624        stack_size: usize,
625        name: Option<String>,
626        mut initial_clock: Option<VectorClock>,
627        caller: &'static Location<'static>,
628    ) -> TaskId {
629        thread::switch();
630        let task_id = Self::with(|state| {
631            let parent_span_id = state.top_level_span.id();
632            let task_id = TaskId(state.tasks.len());
633            let tag = state.get_tag_or_default_for_current_task();
634
635            Self::set_labels_for_new_task(state, task_id, name.clone());
636
637            let clock = if let Some(ref mut clock) = initial_clock {
638                clock
639            } else {
640                // Inherit the clock of the parent thread (which spawned this task)
641                state.increment_clock_mut()
642            };
643            clock.extend(task_id); // and extend it with an entry for the new thread
644            let clock = clock.clone();
645
646            let task = Task::from_closure(
647                f,
648                stack_size,
649                task_id,
650                name,
651                clock,
652                parent_span_id,
653                CurrentSchedule::len(),
654                tag,
655                Some(state.current().id()),
656                state.current_mut().signature.new_child(caller),
657            );
658            state.add_task(task);
659
660            task_id
661        });
662        crate::annotations::record_task_created(task_id, false);
663        task_id
664    }
665
666    /// Prepare this ExecutionState to be dropped. Call this before dropping so that the tasks have
667    /// a chance to run their drop handlers while `EXECUTION_STATE` is still in scope.
668    fn cleanup() {
669        // A slightly delicate dance here: we need to drop the tasks from outside of `Self::with`,
670        // because a task's Drop impl might want to call back into `ExecutionState` (to check
671        // `should_stop()`). So we pull the tasks out of the `ExecutionState`, leaving it in an
672        // invalid state, but no one should still be accessing the tasks anyway.
673        let (mut tasks, final_state) = Self::with(|state| {
674            state.in_cleanup = true;
675            assert!(state.current_task == ScheduledTask::Stopped || state.current_task == ScheduledTask::Finished);
676            // Keep the `live_tasks` invariant intact as the tasks are pulled out of the state.
677            state.live_tasks.clear();
678            (std::mem::take(&mut state.tasks), state.current_task)
679        });
680
681        for task in tasks.drain(..) {
682            assert!(
683                final_state == ScheduledTask::Stopped || task.finished() || task.detached,
684                "execution finished but task is not"
685            );
686            Rc::try_unwrap(task.continuation)
687                .map_err(|_| ())
688                .expect("couldn't cleanup a future");
689        }
690
691        while Self::with(|state| state.storage.pop()).is_some() {}
692
693        TASK_ID_TO_TAGS.with(|cell| cell.borrow_mut().clear());
694        LABELS.with(|cell| cell.borrow_mut().clear());
695
696        #[cfg(debug_assertions)]
697        Self::with(|state| state.has_cleaned_up = true);
698
699        Self::with(|state| state.in_cleanup = false);
700    }
701
702    /// Determine whether the execution has finished.
703    pub fn is_finished(&self) -> bool {
704        self.current_task == ScheduledTask::Stopped || self.current_task == ScheduledTask::Finished
705    }
706
707    /// Invoke the scheduler to decide which task to schedule next. Returns true if the chosen task
708    /// is different from the currently running task, indicating that the current task should yield
709    /// its execution.
710    pub fn maybe_yield() -> bool {
711        Self::with(|state| {
712            if std::thread::panicking() && !state.in_cleanup {
713                return true;
714            }
715
716            debug_assert!(
717                matches!(state.current_task, ScheduledTask::Some(_) | ScheduledTask::Finished)
718                    && state.next_task == ScheduledTask::None,
719                "we're inside a task and scheduler should not yet have run"
720            );
721
722            let result = state.schedule();
723            // If scheduling failed, yield so that the outer scheduling loop can handle it.
724            if result.is_err() {
725                return true;
726            }
727
728            // If the next task is the same as the current one, we can skip the context switch
729            // and just advance to the next task immediately.
730            if state.current_task == state.next_task {
731                state.advance_to_next_task();
732                false
733            } else {
734                true
735            }
736        })
737    }
738
739    /// Tell the scheduler that the next context switch is an explicit yield requested by the
740    /// current task. Some schedulers use this as a hint to influence scheduling.
741    pub fn request_yield() {
742        Self::with(|state| {
743            state.has_yielded = true;
744        });
745    }
746
747    /// Check whether the current execution has stopped. Call from `Drop` handlers to early exit if
748    /// they are being invoked because an execution has stopped.
749    ///
750    /// We also stop if we are currently panicking (e.g., perhaps we're unwinding the stack for a
751    /// panic triggered while someone held a Mutex, and so are executing the Drop handler for
752    /// MutexGuard). This avoids calling back into the scheduler during a panic, because the state
753    /// may be poisoned or otherwise invalid.
754    pub fn should_stop() -> bool {
755        std::thread::panicking()
756            || Self::with(|s| {
757                assert_ne!(s.current_task, ScheduledTask::Finished);
758                s.current_task == ScheduledTask::Stopped
759            })
760    }
761
762    /// Generate some diagnostic information used when persisting failures.
763    ///
764    /// Because this method may be called from a panic hook, it must not panic.
765    pub fn failing_task() -> String {
766        Self::try_with(|state| {
767            if let Some(task) = state.try_current() {
768                task.name().unwrap_or_else(|| format!("task-{:?}", task.id().0))
769            } else {
770                "<unknown>".into()
771            }
772        })
773        .unwrap_or_else(|e| format!("Tried to get ExecutionState, but got the following error: {e:?}"))
774    }
775
776    /// Generate a random u64 from the current scheduler and return it.
777    #[inline]
778    pub fn next_u64() -> u64 {
779        Self::with(|state| {
780            CurrentSchedule::push_random();
781            state.scheduler.borrow_mut().next_u64()
782        })
783    }
784
785    pub fn current(&self) -> &Task {
786        self.get(self.current_task.id().unwrap())
787    }
788
789    pub fn current_mut(&mut self) -> &mut Task {
790        self.get_mut(self.current_task.id().unwrap())
791    }
792
793    pub fn try_current(&self) -> Option<&Task> {
794        self.try_get(self.current_task.id()?)
795    }
796
797    pub fn get(&self, id: TaskId) -> &Task {
798        self.try_get(id).unwrap()
799    }
800
801    /// Register a newly created task. Task ids are handed out sequentially as `tasks.len()`, so the
802    /// new id is always greater than every existing one and `live_tasks` stays sorted.
803    fn add_task(&mut self, task: Task) {
804        debug_assert!(self.live_tasks.last().is_none_or(|last| *last < task.id()));
805        self.live_tasks.push(task.id());
806        self.tasks.push(task);
807    }
808
809    /// Mark the task as finished and drop it from the set of live tasks.
810    fn finish_task(&mut self, task_id: TaskId) {
811        self.get_mut(task_id).finish();
812        let idx = self
813            .live_tasks
814            .binary_search(&task_id)
815            .expect("finished task must be live");
816        self.live_tasks.remove(idx);
817    }
818
819    /// Mark the current task as finished and drop it from the set of live tasks.
820    fn finish_current_task(&mut self) {
821        self.finish_task(self.current_task.id().unwrap());
822    }
823
824    pub fn get_mut(&mut self, id: TaskId) -> &mut Task {
825        self.tasks.get_mut(id.0).unwrap()
826    }
827
828    pub fn try_get(&self, id: TaskId) -> Option<&Task> {
829        self.tasks.get(id.0)
830    }
831
832    pub fn in_cleanup(&self) -> bool {
833        self.in_cleanup
834    }
835
836    pub fn context_switches() -> usize {
837        Self::with(|state| state.context_switches)
838    }
839
840    #[track_caller]
841    pub fn new_resource_signature(resource_type: ResourceType) -> ResourceSignature {
842        ExecutionState::with(|s| s.current_mut().signature.new_resource(resource_type))
843    }
844
845    pub fn get_storage<K: Into<StorageKey>, T: 'static>(&self, key: K) -> Option<&T> {
846        self.storage
847            .get(key.into())
848            .map(|result| result.expect("global storage is never destructed"))
849    }
850
851    pub fn init_storage<K: Into<StorageKey>, T: 'static>(&mut self, key: K, value: T) {
852        self.storage.init(key.into(), value);
853    }
854
855    pub fn get_clock(&self, id: TaskId) -> &VectorClock {
856        &self.tasks.get(id.0).unwrap().clock
857    }
858
859    pub fn get_clock_mut(&mut self, id: TaskId) -> &mut VectorClock {
860        &mut self.tasks.get_mut(id.0).unwrap().clock
861    }
862
863    /// Increment the current thread's clock entry and update its clock with the one provided.
864    pub fn update_clock(&mut self, clock: &VectorClock) {
865        let task = self.current_mut();
866        task.clock.increment(task.id);
867        task.clock.update(clock);
868    }
869
870    /// Increment the current thread's clock and return a shared reference to it
871    pub fn increment_clock(&mut self) -> &VectorClock {
872        let task = self.current_mut();
873        task.clock.increment(task.id);
874        &task.clock
875    }
876
877    /// Increment the current thread's clock and return a mutable reference to it
878    pub fn increment_clock_mut(&mut self) -> &mut VectorClock {
879        let task = self.current_mut();
880        task.clock.increment(task.id);
881        &mut task.clock
882    }
883
884    /// Returns `true` if the test has exceeded the step bound, and `false` otherwise.
885    fn is_step_bound_exceeded(&self, max_steps: usize) -> bool {
886        CurrentSchedule::len() - self.steps_reset_at >= max_steps
887    }
888
889    /// Run the scheduler to choose the next task to run. `has_yielded` should be false if the
890    /// scheduler is being invoked from within a running task. If scheduling fails, returns an Err
891    /// with a String describing the failure.
892    fn schedule(&mut self) -> Result<(), StepError> {
893        // Don't schedule twice. If `maybe_yield` ran the scheduler, we don't want to run it
894        // again at the top of `step`.
895        if self.next_task != ScheduledTask::None {
896            return Ok(());
897        }
898
899        self.context_switches += 1;
900
901        match self.config.max_steps {
902            MaxSteps::FailAfter(max_steps) => {
903                if self.is_step_bound_exceeded(max_steps) {
904                    return Err(StepError::StepBoundExceeded);
905                }
906            }
907            MaxSteps::ContinueAfter(max_steps) => {
908                if self.is_step_bound_exceeded(max_steps) {
909                    // TODO: We have to set `Stopped` and return `Ok` here, else assertions will fail. This should probably be cleaned up.
910                    self.next_task = ScheduledTask::Stopped;
911                    return Ok(());
912                }
913            }
914            MaxSteps::None => {}
915        }
916
917        let mut unfinished_attached = false;
918        let mut all_runnable_detached = true;
919        let mut any_runnable = false;
920
921        // The loop below only looks at `live_tasks`, so a task missing from that set would silently
922        // never be scheduled. Check that direction of the invariant here; the loop itself checks the
923        // other direction (that no finished task is still in the set).
924        debug_assert!(
925            self.tasks
926                .iter()
927                .filter(|task| !task.finished() && task.runnable())
928                .all(|task| self.live_tasks.binary_search(&task.id()).is_ok()),
929            "live_tasks is missing a runnable unfinished task"
930        );
931
932        for &task_id in &self.live_tasks {
933            let task = &self.tasks[task_id.0];
934            debug_assert!(!task.finished());
935            unfinished_attached |= !task.detached;
936            let is_runnable = task.runnable();
937            any_runnable |= is_runnable;
938
939            if is_runnable {
940                all_runnable_detached &= task.detached;
941                self.runnable_tasks.push(task as *const Task);
942            } else if task.can_spuriously_wakeup() {
943                // Some blocked tasks can be woken up spuriously, even though the condition the task is
944                // blocked on hasn't happened yet. We'll add such tasks to the list of runnable tasks, but
945                // they won't contribute to the check on `any_runnable`; if the only runnable tasks
946                // are ones that are waiting for a potential spurious wakeup, it should still be treated as
947                // a deadlock since there's no guarantee that spurious wakeups will ever occur.
948                self.runnable_tasks.push(task as *const Task);
949            }
950        }
951
952        // We should finish execution when either
953        // (1) There are no runnable tasks, or
954        // (2) All runnable tasks have been detached AND there are no unfinished attached tasks
955        // If there are some unfinished attached tasks and all runnable tasks are detached, we must
956        // run some detached task to give them a chance to unblock some unfinished attached task.
957        if !any_runnable || (!unfinished_attached && all_runnable_detached) {
958            self.next_task = ScheduledTask::Finished;
959            return Ok(());
960        }
961
962        let is_yielding = std::mem::replace(&mut self.has_yielded, false);
963
964        // Cast the slice of raw pointers to a slice of references in place to provide schedulers with a safe API
965        //
966        // SAFETY: This is safe because the tasks themselves are only being accessed through this shared reference by the
967        // schedulers, and all references are always cleared from the runnable_tasks Vec at the end of this function.
968        // The transmute itself is safe because *const and & have the same layout, and the pointer is created from a
969        // reference earlier in this function.
970        let task_refs = unsafe { std::mem::transmute::<&[*const Task], &[&Task]>(&self.runnable_tasks) };
971
972        self.next_task = self
973            .scheduler
974            .borrow_mut()
975            .next_task(task_refs, self.current_task.id(), is_yielding)
976            .map(ScheduledTask::Some)
977            .unwrap_or(ScheduledTask::Stopped);
978
979        // Tracing this `in_scope` is purely a matter of taste. We do it because
980        // 1) It is an action taken by the scheduler, and should thus be traced under the scheduler's span
981        // 2) It creates a visual separation of scheduling decisions and `Task`-induced tracing.
982        // Note that there is a case to be made for not `in_scope`-ing it, as that makes seeing the context
983        // of the context switch clearer.
984        //
985        // Note also that changing this trace! statement requires changing the test `basic::labels::test_tracing_with_label_fn`
986        // which relies on this trace reporting the `runnable` tasks.
987        self.top_level_span.in_scope(|| {
988            trace!(
989                i=CurrentSchedule::len(),
990                next_task=?self.next_task,
991                runnable=?task_refs.iter().map(|task| task.id()).collect::<SmallVec<[_; DEFAULT_INLINE_TASKS]>>(),
992                "scheduling decision"
993            );
994        });
995
996        // If the task chosen by the scheduler is blocked, then it should be one that can be
997        // spuriously woken up, and we need to unblock it here so that it can execute.
998        if let Some(tid) = self.next_task.id() {
999            let task = self.get_mut(tid);
1000            assert!(task.runnable() || task.blocked());
1001            if task.blocked() {
1002                assert!(task.can_spuriously_wakeup());
1003                task.unblock();
1004            }
1005        }
1006
1007        // Retains the capacity of `runnable_tasks` for future calls of `schedule`
1008        self.runnable_tasks.clear();
1009
1010        Ok(())
1011    }
1012
1013    /// Set the next task as the current task
1014    fn advance_to_next_task(&mut self) {
1015        debug_assert_ne!(self.next_task, ScheduledTask::None);
1016        self.current_task = self.next_task.take();
1017
1018        if let ScheduledTask::Some(tid) = self.current_task {
1019            CurrentSchedule::push_task(tid);
1020        }
1021    }
1022
1023    // Sets the `tag` field of the current task.
1024    // Returns the `tag` which was there previously.
1025    #[allow(deprecated)]
1026    pub fn set_tag_for_current_task(tag: Arc<dyn Tag>) -> Option<Arc<dyn Tag>> {
1027        ExecutionState::with(|s| s.current_mut().set_tag(tag))
1028    }
1029
1030    #[allow(deprecated)]
1031    fn get_tag_or_default_for_current_task(&self) -> Option<Arc<dyn Tag>> {
1032        self.try_current().and_then(|current| current.get_tag())
1033    }
1034
1035    #[allow(deprecated)]
1036    pub fn get_tag_for_current_task() -> Option<Arc<dyn Tag>> {
1037        ExecutionState::with(|s| s.get_tag_or_default_for_current_task())
1038    }
1039
1040    #[allow(deprecated)]
1041    pub fn set_tag_for_task(task: TaskId, tag: Arc<dyn Tag>) -> Option<Arc<dyn Tag>> {
1042        ExecutionState::with(|s| s.get_mut(task).set_tag(tag))
1043    }
1044}
1045
1046#[cfg(debug_assertions)]
1047impl Drop for ExecutionState {
1048    fn drop(&mut self) {
1049        assert!(self.has_cleaned_up || std::thread::panicking());
1050    }
1051}