Skip to content

Commit c11a44a

Browse files
committed
Use more precise atomic orderings
1 parent 88c70ed commit c11a44a

File tree

1 file changed

+41
-12
lines changed

1 file changed

+41
-12
lines changed

src/libstd/sync/once.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,38 @@
5151
//
5252
// You'll find a few more details in the implementation, but that's the gist of
5353
// it!
54+
//
55+
// Atomic orderings:
56+
// When running `Once` we deal with multiple atomics:
57+
// `Once.state_and_queue` and an unknown number of `Waiter.signaled`.
58+
// * `state_and_queue` is used (1) as a state flag, (2) for synchronizing the
59+
// result of the `Once`, and (3) for synchronizing `Waiter` nodes.
60+
// - At the end of the `call_inner` function we have to make sure the result
61+
// of the `Once` is acquired. So every load which can be the only one to
62+
// load COMPLETED must have at least Acquire ordering, which means all
63+
// three of them.
64+
// - `WaiterQueue::Drop` is the only place that may store COMPLETED, and
65+
// must do so with Release ordering to make the result available.
66+
// - `wait` inserts `Waiter` nodes as a pointer in `state_and_queue`, and
67+
// needs to make the nodes available with Release ordering. The load in
68+
// its `compare_and_swap` can be Relaxed because it only has to compare
69+
// the atomic, not to read other data.
70+
// - `WaiterQueue::Drop` must see the `Waiter` nodes, so it must load
71+
// `state_and_queue` with Acquire ordering.
72+
// - There is just one store where `state_and_queue` is used only as a
73+
// state flag, without having to synchronize data: switching the state
74+
// from INCOMPLETE to RUNNING in `call_inner`. This store can be Relaxed,
75+
// but the read has to be Acquire because of the requirements mentioned
76+
// above.
77+
// * `Waiter.signaled` is both used as a flag, and to protect a field with
78+
// interior mutability in `Waiter`. `Waiter.thread` is changed in
79+
// `WaiterQueue::Drop` which then sets `signaled` with Release ordering.
80+
// After `wait` loads `signaled` with Acquire and sees it is true, it needs to
81+
// see the changes to drop the `Waiter` struct correctly.
82+
// * There is one place where the two atomics `Once.state_and_queue` and
83+
// `Waiter.signaled` come together, and might be reordered by the compiler or
84+
// processor. Because both use Aquire ordering such a reordering is not
85+
// allowed, so no need for SeqCst.
5486

5587
use crate::cell::Cell;
5688
use crate::fmt;
@@ -337,7 +369,7 @@ impl Once {
337369
// An `Acquire` load is enough because that makes all the initialization
338370
// operations visible to us, and, this being a fast path, weaker
339371
// ordering helps with performance. This `Acquire` synchronizes with
340-
// `SeqCst` operations on the slow path.
372+
// `Release` operations on the slow path.
341373
self.state_and_queue.load(Ordering::Acquire) == COMPLETE
342374
}
343375

@@ -355,12 +387,9 @@ impl Once {
355387
#[cold]
356388
fn call_inner(&self,
357389
ignore_poisoning: bool,
358-
init: &mut dyn FnMut(bool)) {
359-
360-
// This cold path uses SeqCst consistently because the
361-
// performance difference really does not matter there, and
362-
// SeqCst minimizes the chances of something going wrong.
363-
let mut state_and_queue = self.state_and_queue.load(Ordering::SeqCst);
390+
init: &mut dyn FnMut(bool))
391+
{
392+
let mut state_and_queue = self.state_and_queue.load(Ordering::Acquire);
364393
loop {
365394
match state_and_queue {
366395
COMPLETE => break,
@@ -373,7 +402,7 @@ impl Once {
373402
// Try to register this thread as the one RUNNING.
374403
let old = self.state_and_queue.compare_and_swap(state_and_queue,
375404
RUNNING,
376-
Ordering::SeqCst);
405+
Ordering::Acquire);
377406
if old != state_and_queue {
378407
state_and_queue = old;
379408
continue
@@ -395,7 +424,7 @@ impl Once {
395424
// pointer to the waiter queue in the more significant bits.
396425
assert!(state_and_queue & STATE_MASK == RUNNING);
397426
wait(&self.state_and_queue, state_and_queue);
398-
state_and_queue = self.state_and_queue.load(Ordering::SeqCst);
427+
state_and_queue = self.state_and_queue.load(Ordering::Acquire);
399428
}
400429
}
401430
}
@@ -438,7 +467,7 @@ fn wait(state_and_queue: &AtomicUsize, current_state: usize) {
438467
// drop our `Waiter` node and leave a hole in the linked list (and a
439468
// dangling reference). Guard against spurious wakeups by reparking
440469
// ourselves until we are signaled.
441-
while !node.signaled.load(Ordering::SeqCst) {
470+
while !node.signaled.load(Ordering::Acquire) {
442471
thread::park();
443472
}
444473
}
@@ -454,7 +483,7 @@ impl Drop for WaiterQueue<'_> {
454483
fn drop(&mut self) {
455484
// Swap out our state with however we finished.
456485
let state_and_queue = self.state_and_queue.swap(self.set_state_on_drop_to,
457-
Ordering::SeqCst);
486+
Ordering::AcqRel);
458487

459488
// We should only ever see an old state which was RUNNING.
460489
assert_eq!(state_and_queue & STATE_MASK, RUNNING);
@@ -470,7 +499,7 @@ impl Drop for WaiterQueue<'_> {
470499
while !queue.is_null() {
471500
let next = (*queue).next;
472501
let thread = (*queue).thread.replace(None).unwrap();
473-
(*queue).signaled.store(true, Ordering::SeqCst);
502+
(*queue).signaled.store(true, Ordering::Release);
474503
// ^- FIXME (maybe): This is another case of issue #55005
475504
// `store()` has a potentially dangling ref to `signaled`.
476505
queue = next;

0 commit comments

Comments
 (0)