shuttle_engine/current.rs
1//! Information about the current thread and current Shuttle execution.
2//!
3//! This module provides access to information about the current Shuttle execution. It is useful for
4//! building tools that need to exploit Shuttle's total ordering of concurrent operations; for
5//! example, a tool that wants to check linearizability might want access to a global timestamp for
6//! events, which the [`context_switches`] function provides.
7//!
8//! This module also provides functions to manage the assocation of `labels` to threads and async tasks.
9//! Labels are typed values that can be associated with a task. They are useful for debugging: for
10//! instance, the `TaskName` label can be set to assign names to tasks to make debug output easier to read.
11//! Labels can also be used to build customized schedulers: for instance, they can be used to assign
12//! numeric weights to tasks, which can be used to implement a priority-preemptive scheduler.
13
14#[allow(deprecated)]
15use crate::runtime::execution::TASK_ID_TO_TAGS;
16use crate::runtime::execution::{ExecutionState, LABELS};
17use crate::runtime::task::clock::VectorClock;
18pub use crate::runtime::task::labels::Labels;
19pub use crate::runtime::task::{ChildLabelFn, TaskId, TaskName};
20#[allow(deprecated)]
21pub use crate::runtime::task::{Tag, Taggable};
22use std::fmt::Debug;
23use std::sync::Arc;
24
25/// The number of context switches that happened so far in the current Shuttle execution.
26///
27/// Note that this is the number of *possible* context switches, i.e., including times when the
28/// scheduler decided to continue with the same task. This means the result can be used as a
29/// timestamp for atomic actions during an execution.
30///
31/// Panics if called outside of a Shuttle execution.
32pub fn context_switches() -> usize {
33 ExecutionState::context_switches()
34}
35
36/// Get the current thread's vector clock, or an empty clock if there is no current task.
37///
38/// Every `BatchSemaphore` operation calls this, so a `Drop` handler that touches a modelled
39/// primitive reaches it wherever the value is dropped: outside an execution, or while
40/// `ExecutionState` is borrowed. Panicking there happens inside a destructor, which can abort the
41/// process instead of reporting the failure under investigation. (While an execution is torn down,
42/// the task being torn down is the current task.)
43pub fn clock() -> VectorClock {
44 ExecutionState::try_with(|state| {
45 let id = state.try_current().map(|me| me.id());
46 id.map(|id| state.get_clock(id).clone())
47 })
48 .ok()
49 .flatten()
50 .unwrap_or_else(VectorClock::new)
51}
52
53/// Gets the clock for the thread with the given task ID
54pub fn clock_for(task_id: TaskId) -> VectorClock {
55 ExecutionState::with(|state| state.get_clock(task_id).clone())
56}
57
58/// Apply the given function to the Labels for the specified task
59pub fn with_labels_for_task<F, T>(task_id: TaskId, f: F) -> T
60where
61 F: FnOnce(&mut Labels) -> T,
62{
63 LABELS.with(|cell| {
64 let mut map = cell.borrow_mut();
65 let m = map.entry(task_id).or_default();
66 f(m)
67 })
68}
69
70/// Get a label of the given type for the specified task, if any
71pub fn get_label_for_task<T: Clone + Debug + 'static>(task_id: TaskId) -> Option<T> {
72 with_labels_for_task(task_id, |labels| labels.get().cloned())
73}
74
75/// Add the given label to the specified task, returning the old label for the type, if any
76pub fn set_label_for_task<T: Clone + Debug + 'static>(task_id: TaskId, value: T) -> Option<T> {
77 with_labels_for_task(task_id, |labels| labels.insert(value))
78}
79
80/// Remove a label of the given type for the specified task, returning the old label for the type, if any
81pub fn remove_label_for_task<T: Clone + Debug + 'static>(task_id: TaskId) -> Option<T> {
82 with_labels_for_task(task_id, |labels| labels.remove())
83}
84
85/// Get the debug name for a task
86pub fn get_name_for_task(task_id: TaskId) -> Option<TaskName> {
87 get_label_for_task::<TaskName>(task_id)
88}
89
90/// Set the debug name for a task, returning the old name, if any
91pub fn set_name_for_task(task_id: TaskId, task_name: impl Into<TaskName>) -> Option<TaskName> {
92 let task_name = task_name.into();
93 crate::annotations::record_name_for_task(task_id, &task_name);
94 // Do note that `record` simply appends the new name as a field, meaning the step span will end up looking something like this:
95 // step{task="main-thread(2)" task="Child"}
96 // when running with something like the `tracing_subscriber::fmt` subscriber.
97 // This either has to be lived with, or the task name should be set via the `ChildLabelFn` mechanism, or a different subscriber should be used
98 // (if this is done, then `record_steps_in_span` should be set to true as well), or Shuttle will have to be chanegd to recreate the Span
99 let res = ExecutionState::try_with(|state| {
100 state
101 .get_mut(task_id)
102 .step_span
103 .record("task", format!("{task_name:?}"));
104 });
105 if let Err(e) = res {
106 tracing::error!("`set_name_for_task` failed with error: {e:?}");
107 }
108 set_label_for_task::<TaskName>(task_id, task_name)
109}
110
111/// Gets the `TaskId` of the current task, or `None` if there is no current task.
112///
113/// Panics if called outside of a Shuttle execution, or while Shuttle is updating its own state
114/// (which is when it calls `tracing` subscribers for some of its own events). Use
115/// [`try_get_current_task`] in code that can run in those places.
116pub fn get_current_task() -> Option<TaskId> {
117 ExecutionState::with(|s| Some(s.try_current()?.id()))
118}
119
120/// Gets the `TaskId` of the current task, or `None` if there is no current task.
121///
122/// Unlike [`get_current_task`], this never panics, so it is meant for code that can run anywhere,
123/// such as `Drop` handlers and `tracing` subscribers. It returns `None` outside of a Shuttle
124/// execution, and whenever Shuttle is updating its own state, even while a task is running. That
125/// includes the code that Shuttle runs from there: `tracing` subscribers handling some of Shuttle's
126/// own events, `ChildLabelFn`s, and the destructors of labels and tags. Between steps, while
127/// Shuttle picks the next task to run, it returns the task that ran last. While Shuttle tears an
128/// execution down, it returns the task being torn down, and the main thread's while Shuttle drops
129/// statics.
130pub fn try_get_current_task() -> Option<TaskId> {
131 ExecutionState::try_with(|s| Some(s.try_current()?.id())).ok().flatten()
132}
133
134/// Get the `TaskId` of the current task. Panics if there is no current task.
135pub fn me() -> TaskId {
136 get_current_task().unwrap()
137}
138
139/// Sets the number of scheduling steps used (wrt. the step bound) to 0.
140///
141/// The idea behind this is to run the test with some step bound, and then call this function whenever it is known that progress has been made.
142/// This allows tests to run with tighter step bounds, and to scale a test up without also changing the step bound.
143///
144/// NOTE: Be careful when using this, as if used wrongly it can be used to make a test execute forever.
145pub fn reset_step_count() {
146 ExecutionState::with(|s| s.reset_step_count());
147}
148
149/// Sets the `tag` field of the current task.
150/// Returns the `tag` which was there previously.
151#[deprecated]
152#[allow(deprecated)]
153pub fn set_tag_for_current_task(tag: Arc<dyn Tag>) -> Option<Arc<dyn Tag>> {
154 ExecutionState::set_tag_for_current_task(tag)
155}
156
157/// Gets the `tag` field of the current task.
158#[deprecated]
159#[allow(deprecated)]
160pub fn get_tag_for_current_task() -> Option<Arc<dyn Tag>> {
161 ExecutionState::get_tag_for_current_task()
162}
163
164/// Gets the `tag` field of the specified task.
165#[deprecated]
166#[allow(deprecated)]
167pub fn get_tag_for_task(task_id: TaskId) -> Option<Arc<dyn Tag>> {
168 TASK_ID_TO_TAGS.with(|cell| {
169 let map = cell.borrow();
170 map.get(&task_id).cloned()
171 })
172}
173
174/// Sets the `tag` field of the specified task.
175#[deprecated]
176#[allow(deprecated)]
177pub fn set_tag_for_task(task: TaskId, tag: Arc<dyn Tag>) -> Option<Arc<dyn Tag>> {
178 ExecutionState::set_tag_for_task(task, tag)
179}