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
5587use crate :: cell:: Cell ;
5688use 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