#![allow(
unexpected_cfgs,
reason = "`sanitized` is supplied by the internal sanitizer build"
)]
use std::cell::UnsafeCell;
use std::sync::Arc;
#[global_allocator]
static ALLOC: test_allocator::Global = test_allocator::Global;
pub(crate) struct SharedCells(Arc<[UnsafeCell<u64>]>);
unsafe impl Send for SharedCells {}
unsafe impl Sync for SharedCells {}
impl Clone for SharedCells {
fn clone(&self) -> Self {
SharedCells(Arc::clone(&self.0))
}
}
impl SharedCells {
pub(crate) fn zeroed(len: usize) -> Self {
let cells: Vec<UnsafeCell<u64>> = (0..len).map(|_| UnsafeCell::new(0)).collect();
SharedCells(cells.into())
}
pub(crate) fn len(&self) -> usize {
self.0.len()
}
pub(crate) fn get(&self, i: usize) -> u64 {
unsafe { *self.0[i].get() }
}
pub(crate) fn set(&self, i: usize, v: u64) {
unsafe { *self.0[i].get() = v }
}
}
fn count_switch_points(shared_data: &SharedCells) -> u64 {
let mut switch_points = 0;
let mut prev = shared_data.get(0);
for i in 1..shared_data.len() {
if prev != shared_data.get(i) {
prev = shared_data.get(i);
switch_points += 1;
}
}
switch_points
}
mod mem_race {
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::thread;
use detcore_testutils::check_fn_with_config;
use detcore_testutils::det_test_fn_with_config;
use detcore_testutils::expect_success;
const NUM_ELEMENTS: usize = 20_000_000;
fn raw() -> u64 {
let shared_data = super::SharedCells::zeroed(NUM_ELEMENTS);
let shared_idx = Arc::new(AtomicUsize::new(0));
fn worker(idx: Arc<AtomicUsize>, data: super::SharedCells, tag: u64) {
for _ in 0..(NUM_ELEMENTS / 2) {
let idx = idx.fetch_add(1, Ordering::SeqCst);
data.set(idx, tag);
}
}
let handle = {
let (idx, data) = (shared_idx.clone(), shared_data.clone());
thread::spawn(move || {
worker(idx, data, 1)
})
};
println!("Parent done spawning child thread and starting own work...");
worker(shared_idx, shared_data.clone(), 2);
println!("Parent done with work and joining child thread..");
handle.join().unwrap();
let switch_points = super::count_switch_points(&shared_data);
let s: String = format!("Switch points: {}\n", switch_points);
println!("{}", s); switch_points
}
#[test] fn raw_run_par_mode() {
eprintln!("Running in parallel, expecting interleavings.");
if raw() <= 1 {
eprintln!("Expected more than 1 switch point!");
std::process::exit(99);
}
}
fn raw_run_par_mode_reverie() {
if raw() <= 1 {
eprintln!(
"Slightly surprising that there's only 1 switch point under Reverie! But whatever."
);
}
}
#[allow(dead_code)]
fn run_noop_mode() {
check_fn_with_config::<(), _>(raw_run_par_mode_reverie, (), false);
}
#[test]
#[cfg(not(sanitized))]
fn noop_mode() {
run_noop_mode();
}
#[cfg(not(sanitized))]
detcore_testutils::make_det_test_variants!(detcore, "all");
#[allow(dead_code)]
pub fn detcore(cfg: &detcore::Config) {
fn run_seq_mode() {
eprintln!("Running sequentialized, deterministically.");
let switches = raw();
assert!(
switches > 10,
"Expecting deterministic preemptions when using RCB timers"
);
}
let cfg = cfg.clone();
if cfg.sequentialize_threads {
det_test_fn_with_config(true, run_seq_mode, cfg, expect_success);
} else {
det_test_fn_with_config(false, raw_run_par_mode_reverie, cfg, expect_success);
}
}
#[test]
#[cfg(not(sanitized))]
pub fn with_signal() {}
}
mod mem_print_race {
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::thread;
use detcore::Detcore;
use detcore_testutils::test_fn_with_config;
use pretty_assertions::assert_eq;
use reverie::ExitStatus;
const NUM_ELEMENTS: usize = 2_000_000;
const CHUNKS: usize = 5;
fn raw() -> u64 {
let shared_data = super::SharedCells::zeroed(NUM_ELEMENTS);
let shared_idx = Arc::new(AtomicUsize::new(0));
fn worker(idx: Arc<AtomicUsize>, data: super::SharedCells, rank: usize) {
let tid = rank as u64 + 1;
for _i in 0..CHUNKS {
let s = format!("{} ", rank);
eprint!("{}", s);
for _ in 0..(NUM_ELEMENTS / 2 / CHUNKS) {
let idx = idx.fetch_add(1, Ordering::SeqCst);
data.set(idx, tid);
}
}
std::io::Write::flush(&mut std::io::stderr()).unwrap();
}
let handle = {
let (idx, data) = (shared_idx.clone(), shared_data.clone());
thread::spawn(move || worker(idx, data, 0))
};
worker(shared_idx, shared_data.clone(), 1);
handle.join().unwrap();
let switch_points = super::count_switch_points(&shared_data);
let s: String = format!("\nSwitch points: {}\n", switch_points);
println!("{}", s);
switch_points
}
fn raw_assert(thresh: u64) -> impl Fn() {
move || {
eprintln!("Running in parallel, expecting interleavings.");
if raw() <= thresh {
eprintln!("Expected more than {} switch point(s)!", thresh);
std::process::exit(1);
}
}
}
#[test]
fn raw_run_par_mode() {
raw_assert(1)();
}
#[cfg(not(sanitized))]
detcore_testutils::make_det_test_variants!(detcore, "all");
#[allow(dead_code)]
pub fn detcore(cfg: &detcore::Config) {
eprintln!("Running detcore test with {} chunks", CHUNKS);
let cfg = cfg.clone();
let (output, _state) = if cfg.sequentialize_threads {
test_fn_with_config::<Detcore, _>(raw_assert(2 * CHUNKS as u64 - 10), cfg, false)
.unwrap()
} else {
test_fn_with_config::<Detcore, _>(raw_assert(1), cfg, false).unwrap()
};
reverie_ptrace::testing::print_tracee_output(&output);
assert_eq!(output.status, ExitStatus::Exited(0));
}
}
mod futex_wait_parent {
use std::sync::Arc;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::thread;
#[cfg(not(sanitized))]
detcore_testutils::basic_det_test!(
raw,
|c: &detcore::Config| c.sequentialize_threads,
"bottom",
"middle",
"default"
);
#[test]
fn raw() {
let sem = Arc::new(AtomicU32::new(1000));
let ptr = sem.as_ptr();
let ptr2 = ptr as usize;
let sem2 = sem.clone();
eprintln!("Parent thread: spawn child.");
let _ = thread::spawn(move || {
let ptr: *mut u8 = ptr2 as *mut u8;
eprintln!("Child thread start.");
std::thread::sleep(std::time::Duration::from_millis(500));
sem2.fetch_add(1, Ordering::SeqCst);
eprintln!("Child thread: start futex wake ({:?})..", ptr);
let res = unsafe {
libc::syscall(
libc::SYS_futex,
ptr,
libc::FUTEX_WAKE,
1000, 0, 0, 0, )
};
let s = format!("Child thread: futex wake returned {}, done.\n", res);
eprint!("{}", s);
});
eprintln!("Parent thread: futex wait ({:?})..", ptr);
let observation = sem.load(Ordering::SeqCst);
let res = unsafe {
libc::syscall(
libc::SYS_futex,
ptr,
libc::FUTEX_WAIT,
1000, 0, 0, 0, )
};
std::thread::sleep(std::time::Duration::from_millis(500));
let s = format!(
"Parent thread: done with futex wait, rax: {}, observation {}\n",
res, observation
);
eprint!("{}", s);
std::thread::sleep(std::time::Duration::from_millis(500));
eprintln!("Parent thread: exiting process.");
let _ = unsafe { libc::syscall(libc::SYS_exit_group, 0) };
}
}