Skip to content

Commit

Permalink
std: fix busy-waiting in Once::wait_force, add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joboet committed Jul 31, 2024
1 parent 6fd82f1 commit cc6f37f
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 4 deletions.
47 changes: 47 additions & 0 deletions std/src/sync/once/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::Once;
use crate::sync::atomic::AtomicBool;
use crate::sync::atomic::Ordering::Relaxed;
use crate::sync::mpsc::channel;
use crate::time::Duration;
use crate::{panic, thread};

#[test]
Expand Down Expand Up @@ -113,3 +116,47 @@ fn wait_for_force_to_finish() {
assert!(t1.join().is_ok());
assert!(t2.join().is_ok());
}

#[test]
fn wait() {
for _ in 0..50 {
let val = AtomicBool::new(false);
let once = Once::new();

thread::scope(|s| {
for _ in 0..4 {
s.spawn(|| {
once.wait();
assert!(val.load(Relaxed));
});
}

once.call_once(|| val.store(true, Relaxed));
});
}
}

#[test]
fn wait_on_poisoned() {
let once = Once::new();

panic::catch_unwind(|| once.call_once(|| panic!())).unwrap_err();
panic::catch_unwind(|| once.wait()).unwrap_err();
}

#[test]
fn wait_force_on_poisoned() {
let once = Once::new();

thread::scope(|s| {
panic::catch_unwind(|| once.call_once(|| panic!())).unwrap_err();

s.spawn(|| {
thread::sleep(Duration::from_millis(100));

once.call_once_force(|_| {});
});

once.wait_force();
})
}
12 changes: 8 additions & 4 deletions std/src/sys/sync/once/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl Once {
panic!("Once instance has previously been poisoned");
}
_ => {
current = wait(&self.state_and_queue, current);
current = wait(&self.state_and_queue, current, !ignore_poisoning);
}
}
}
Expand Down Expand Up @@ -216,14 +216,18 @@ impl Once {
// All other values must be RUNNING with possibly a
// pointer to the waiter queue in the more significant bits.
assert!(state == RUNNING);
current = wait(&self.state_and_queue, current);
current = wait(&self.state_and_queue, current, true);
}
}
}
}
}

fn wait(state_and_queue: &AtomicPtr<()>, mut current: StateAndQueue) -> StateAndQueue {
fn wait(
state_and_queue: &AtomicPtr<()>,
mut current: StateAndQueue,
return_on_poisoned: bool,
) -> StateAndQueue {
let node = &Waiter {
thread: Cell::new(Some(thread::current())),
signaled: AtomicBool::new(false),
Expand All @@ -235,7 +239,7 @@ fn wait(state_and_queue: &AtomicPtr<()>, mut current: StateAndQueue) -> StateAnd
let queue = to_queue(current);

// If initialization has finished, return.
if matches!(state, POISONED | COMPLETE) {
if state == COMPLETE || (return_on_poisoned && state == POISONED) {
return current;
}

Expand Down

0 comments on commit cc6f37f

Please sign in to comment.