Skip to content

Commit

Permalink
Move clock functions into their own module
Browse files Browse the repository at this point in the history
Just an effort to not pollute the top-level namespace too much. Putting
these into their own module gives us a place to put future functions
that also reveal information about the current execution.
  • Loading branch information
jamesbornholt committed Sep 21, 2021
1 parent b781b1e commit a63064a
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 50 deletions.
34 changes: 34 additions & 0 deletions src/current.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Information about the current thread and current Shuttle execution.
//!
//! This module provides access to information about the current Shuttle execution. It is useful for
//! building tools that need to exploit Shuttle's total ordering of concurrent operations; for
//! example, a tool that wants to check linearizability might want access to a global timestamp for
//! events, which the [`context_switches`] function provides.
use crate::runtime::execution::ExecutionState;
use crate::runtime::task::clock::VectorClock;
use crate::runtime::task::TaskId;

/// The number of context switches that happened so far in the current Shuttle execution.
///
/// Note that this is the number of *possible* context switches, i.e., including times when the
/// scheduler decided to continue with the same task. This means the result can be used as a
/// timestamp for atomic actions during an execution.
///
/// Panics if called outside of a Shuttle execution.
pub fn context_switches() -> usize {
ExecutionState::context_switches()
}

/// Get the current thread's vector clock
pub fn clock() -> VectorClock {
crate::runtime::execution::ExecutionState::with(|state| {
let me = state.current();
state.get_clock(me.id()).clone()
})
}

/// Gets the clock for the thread with the given task ID
pub fn clock_for(task_id: TaskId) -> VectorClock {
ExecutionState::with(|state| state.get_clock(task_id).clone())
}
24 changes: 1 addition & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub mod rand;
pub mod sync;
pub mod thread;

pub mod current;
pub mod scheduler;

mod runtime;
Expand Down Expand Up @@ -372,29 +373,6 @@ where
runner.run(f);
}

/// The number of context switches that happened so far in the current Shuttle execution.
///
/// Note that this is the number of *possible* context switches, i.e., including times when the
/// scheduler decided to continue with the same task.
///
/// Panics if called outside of a Shuttle execution.
pub fn context_switches() -> usize {
crate::runtime::execution::ExecutionState::context_switches()
}

/// Gets the current thread's vector clock
pub fn my_clock() -> crate::runtime::task::clock::VectorClock {
crate::runtime::execution::ExecutionState::with(|state| {
let me = state.current();
state.get_clock(me.id()).clone()
})
}

/// Gets the clock for the thread with the given task_id
pub fn get_clock(task_id: crate::runtime::task::TaskId) -> crate::runtime::task::clock::VectorClock {
crate::runtime::execution::ExecutionState::with(|state| state.get_clock(task_id).clone())
}

/// Declare a new thread local storage key of type [`LocalKey`](crate::thread::LocalKey).
#[macro_export]
macro_rules! thread_local {
Expand Down
6 changes: 3 additions & 3 deletions src/sync/condvar.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::my_clock;
use crate::current;
use crate::runtime::execution::ExecutionState;
use crate::runtime::task::clock::VectorClock;
use crate::runtime::task::TaskId;
Expand Down Expand Up @@ -205,7 +205,7 @@ impl Condvar {
for (tid, status) in state.waiters.iter_mut() {
assert_ne!(*tid, me);

let clock = my_clock();
let clock = current::clock();
match status {
CondvarWaitStatus::Waiting => {
let mut epochs = VecDeque::new();
Expand Down Expand Up @@ -240,7 +240,7 @@ impl Condvar {

for (tid, status) in state.waiters.iter_mut() {
assert_ne!(*tid, me);
*status = CondvarWaitStatus::Broadcast(my_clock());
*status = CondvarWaitStatus::Broadcast(current::clock());
// Note: the task might have been unblocked by a previous signal
ExecutionState::with(|s| s.get_mut(*tid).unblock());
}
Expand Down
24 changes: 12 additions & 12 deletions tests/basic/clocks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use shuttle::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use shuttle::sync::mpsc::{channel, sync_channel};
use shuttle::sync::{Barrier, Condvar, Mutex, Once, RwLock};
use shuttle::{check_dfs, check_pct, thread};
use shuttle::{check_dfs, check_pct, current, thread};
use std::collections::HashSet;
use std::sync::Arc;
use test_env_log::test;
Expand All @@ -12,11 +12,11 @@ pub fn me() -> usize {

// TODO Maybe make this a macro so backtraces are more informative
pub fn check_clock(f: impl Fn(usize, u32) -> bool) {
for (i, &c) in shuttle::my_clock().iter().enumerate() {
for (i, &c) in current::clock().iter().enumerate() {
assert!(
f(i, c),
"clock {:?} doesn't satisfy predicate at {}",
shuttle::my_clock(),
current::clock(),
i
);
}
Expand Down Expand Up @@ -298,7 +298,7 @@ fn clock_mpsc_unbounded() {
}
});
for _ in 0..NUM_MSG {
let c1 = shuttle::my_clock().get(1); // save clock of thread 1
let c1 = current::clock().get(1); // save clock of thread 1
let _ = rx.recv().unwrap();
check_clock(|i, c| (i != 1) || (c > c1)); // thread 1's clock increased
}
Expand All @@ -322,23 +322,23 @@ fn clock_mpsc_bounded() {
check_clock(|i, c| (c > 0) == (i == 0 || i == 1));
tx.send(()).unwrap();
// Here, we know the receiver picked up the 1st message, so its clock is nonzero
let c1 = shuttle::my_clock().get(2);
let c1 = current::clock().get(2);
assert!(c1 > 0);
tx.send(()).unwrap();
// Here, we know that the receiver picked up the 2nd message, so its clock has increased
assert!(shuttle::my_clock().get(2) > c1);
assert!(current::clock().get(2) > c1);
});
thread::spawn(move || {
assert_eq!(me(), 2);
// Receiver doesn't know about the sender yet
check_clock(|i, c| (c > 0) == (i == 0));
rx.recv().unwrap();
// The sender has sent a message, so its clock is nonzero
let c1 = shuttle::my_clock().get(1);
let c1 = current::clock().get(1);
assert!(c1 > 0);
let _ = rx.recv().unwrap();
// The sender has sent another message, so its clock has increased
assert!(shuttle::my_clock().get(2) > c1);
assert!(current::clock().get(2) > c1);
// Receive the remaining messages
for _ in 0..BOUND {
rx.recv().unwrap();
Expand All @@ -360,23 +360,23 @@ fn clock_mpsc_rendezvous() {
check_clock(|i, c| (c > 0) == (i == 0));
tx.send(()).unwrap();
// Since this is a rendezvous channel, and we successfully sent a message, we know about the receiver
let c1 = shuttle::my_clock().get(2);
let c1 = current::clock().get(2);
assert!(c1 > 0);
tx.send(()).unwrap();
// After the 2nd rendezvous, the receiver's clock has increased
assert!(shuttle::my_clock().get(2) > c1);
assert!(current::clock().get(2) > c1);
});
thread::spawn(move || {
assert_eq!(me(), 2);
// At this point the receiver doesn't know about the sender
check_clock(|i, c| (c > 0) == (i == 0));
rx.recv().unwrap();
// Since we received a message, we know about the sender
let c1 = shuttle::my_clock().get(1);
let c1 = current::clock().get(1);
assert!(c1 > 0);
rx.recv().unwrap();
// After the 2nd rendezvous, the sender's clock has increased
assert!(shuttle::my_clock().get(1) > c1);
assert!(current::clock().get(1) > c1);
});
},
None,
Expand Down
22 changes: 11 additions & 11 deletions tests/basic/execution.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use shuttle::scheduler::RandomScheduler;
use shuttle::{check, check_dfs, thread, Config, MaxSteps, Runner};
use shuttle::{check, check_dfs, current, thread, Config, MaxSteps, Runner};
use std::panic::{catch_unwind, AssertUnwindSafe};
use test_env_log::test;
// Not actually trying to explore interleavings involving AtomicUsize, just using to smuggle a
Expand Down Expand Up @@ -162,7 +162,7 @@ fn max_steps_early_exit_scheduler() {
#[test]
#[should_panic]
fn context_switches_outside_execution() {
shuttle::context_switches();
current::context_switches();
}

#[test]
Expand All @@ -180,7 +180,7 @@ fn context_switches_atomic() {
let mut threads = vec![];
let counter = Arc::new(shuttle::sync::atomic::AtomicUsize::new(0));

assert_eq!(shuttle::context_switches(), 1);
assert_eq!(current::context_switches(), 1);

for _ in 0..2 {
let counter = Arc::clone(&counter);
Expand All @@ -190,18 +190,18 @@ fn context_switches_atomic() {

// We saw the initial context switch, the spawn and first context switch for each `fetch_add`,
// and the second context switch after the `fetch_add` of this thread.
assert!(shuttle::context_switches() >= 2 + 2 * count);
assert!(current::context_switches() >= 2 + 2 * count);

// We did not see the last context switch of this thread.
assert!(shuttle::context_switches() < EXPECTED_CONTEXT_SWITCHES);
assert!(current::context_switches() < EXPECTED_CONTEXT_SWITCHES);
}));
}

for thread in threads {
thread.join().unwrap();
}

assert_eq!(shuttle::context_switches(), EXPECTED_CONTEXT_SWITCHES);
assert_eq!(current::context_switches(), EXPECTED_CONTEXT_SWITCHES);
},
None,
);
Expand All @@ -216,21 +216,21 @@ fn context_switches_mutex() {
let mutex1 = Arc::new(Mutex::new(0));
let mutex2 = Arc::new(Mutex::new(0));

assert_eq!(shuttle::context_switches(), 1);
assert_eq!(current::context_switches(), 1);

{
let mutex1 = mutex1.lock().unwrap();
assert_eq!(shuttle::context_switches(), 2);
assert_eq!(current::context_switches(), 2);
{
let mutex2 = mutex2.lock().unwrap();
assert_eq!(shuttle::context_switches(), 3);
assert_eq!(current::context_switches(), 3);
drop(mutex2);
}
assert_eq!(shuttle::context_switches(), 4);
assert_eq!(current::context_switches(), 4);
drop(mutex1);
}

assert_eq!(shuttle::context_switches(), 5);
assert_eq!(current::context_switches(), 5);
},
None,
);
Expand Down
2 changes: 1 addition & 1 deletion tests/basic/mpsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ fn mpsc_bounded_sum() {
let handle = thread::spawn(move || {
let mut sum = 0;
for _ in 0..5 {
let c1 = shuttle::my_clock().get(1); // save knowledge of sender's clock
let c1 = shuttle::current::clock().get(1); // save knowledge of sender's clock
sum += rx.recv().unwrap();
check_clock(|i, c| (i != 1) || (c > c1)); // sender's clock must have increased
}
Expand Down

0 comments on commit a63064a

Please sign in to comment.