-
Notifications
You must be signed in to change notification settings - Fork 125
Description
Loom uses a per-thread state to remember the status of threads at branching points.
When a newly spawned thread immediately does an atomic load, the code in Execution::schedule ends up setting that to Thread::Skip, disabling any exploration, which is clearly wrong.
This reproduces the case:
#[test]
fn thread_skipped() {
let solutions = std::sync::Arc::new(std::sync::Mutex::new(HashSet::new()));
let solutions1 = std::sync::Arc::clone(&solutions);
loom::model(move || {
let a1 = Arc::new(AtomicBool::new(false));
let a2: Arc<AtomicBool> = a1.clone();
let th = thread::spawn(move || {
// Some irrelevant load, which causes loom to not explore properly.
let _irrelevant = a1.load(Relaxed);
a1.store(true, Relaxed);
});
let update_done = a2.load(Relaxed); // Could be either true or false
th.join().unwrap();
solutions1.lock().unwrap().insert(update_done);
});
let solutions = solutions.lock().unwrap();
assert!(solutions.contains(&false));
assert!(solutions.contains(&true));
assert_eq!(solutions.len(), 2);
}Branch number 0 then finds no alternative paths and exploration ends after one iteration.
Remove the let _irrelevant = a1.load(Relaxed); and exploration happens as expected.
The code in Execution is a bit suspect:
let next = self.path.branch_thread(self.id, {
self.threads.iter().map(|(i, th)| {
if initial.is_none() && th.is_runnable() {
initial = Some(i);
}
if initial == Some(i) {
Thread::Active
} else if th.is_yield() {
Thread::Yield
} else if !th.is_runnable() {
Thread::Disabled
} else {
Thread::Skip // <--- here
}
})
});This sets the thread to Skip even though it is runnable. However, changing this to Pending makes this case work, but breaks some other tests as some schedulings (e.g. thread creations?) count as preemptions, making loom eventually hit the assert in path.rs:
debug_assert!(
self.preemption_bound.is_none() || Some(preemptions) <= self.preemption_bound,
"[loom internal bug] max = {:?}; curr = {}",
self.preemption_bound,
preemptions,
);I can't follow the code well enough yet to prepare a fix. Any pointers as to what should actually happen would be highly appreciated.
I guess a workaround is to start each spawned thread with some otherwise useless store?
Edit: Tried that, but the same issue occurs in some other circumstances as well.