51
51
//
52
52
// You'll find a few more details in the implementation, but that's the gist of
53
53
// 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.
54
86
55
87
use crate :: cell:: Cell ;
56
88
use crate :: fmt;
@@ -337,7 +369,7 @@ impl Once {
337
369
// An `Acquire` load is enough because that makes all the initialization
338
370
// operations visible to us, and, this being a fast path, weaker
339
371
// ordering helps with performance. This `Acquire` synchronizes with
340
- // `SeqCst ` operations on the slow path.
372
+ // `Release ` operations on the slow path.
341
373
self . state_and_queue . load ( Ordering :: Acquire ) == COMPLETE
342
374
}
343
375
@@ -355,12 +387,9 @@ impl Once {
355
387
#[ cold]
356
388
fn call_inner ( & self ,
357
389
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 ) ;
364
393
loop {
365
394
match state_and_queue {
366
395
COMPLETE => break ,
@@ -373,7 +402,7 @@ impl Once {
373
402
// Try to register this thread as the one RUNNING.
374
403
let old = self . state_and_queue . compare_and_swap ( state_and_queue,
375
404
RUNNING ,
376
- Ordering :: SeqCst ) ;
405
+ Ordering :: Acquire ) ;
377
406
if old != state_and_queue {
378
407
state_and_queue = old;
379
408
continue
@@ -395,7 +424,7 @@ impl Once {
395
424
// pointer to the waiter queue in the more significant bits.
396
425
assert ! ( state_and_queue & STATE_MASK == RUNNING ) ;
397
426
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 ) ;
399
428
}
400
429
}
401
430
}
@@ -438,7 +467,7 @@ fn wait(state_and_queue: &AtomicUsize, current_state: usize) {
438
467
// drop our `Waiter` node and leave a hole in the linked list (and a
439
468
// dangling reference). Guard against spurious wakeups by reparking
440
469
// ourselves until we are signaled.
441
- while !node. signaled . load ( Ordering :: SeqCst ) {
470
+ while !node. signaled . load ( Ordering :: Acquire ) {
442
471
thread:: park ( ) ;
443
472
}
444
473
}
@@ -454,7 +483,7 @@ impl Drop for WaiterQueue<'_> {
454
483
fn drop ( & mut self ) {
455
484
// Swap out our state with however we finished.
456
485
let state_and_queue = self . state_and_queue . swap ( self . set_state_on_drop_to ,
457
- Ordering :: SeqCst ) ;
486
+ Ordering :: AcqRel ) ;
458
487
459
488
// We should only ever see an old state which was RUNNING.
460
489
assert_eq ! ( state_and_queue & STATE_MASK , RUNNING ) ;
@@ -470,7 +499,7 @@ impl Drop for WaiterQueue<'_> {
470
499
while !queue. is_null ( ) {
471
500
let next = ( * queue) . next ;
472
501
let thread = ( * queue) . thread . replace ( None ) . unwrap ( ) ;
473
- ( * queue) . signaled . store ( true , Ordering :: SeqCst ) ;
502
+ ( * queue) . signaled . store ( true , Ordering :: Release ) ;
474
503
// ^- FIXME (maybe): This is another case of issue #55005
475
504
// `store()` has a potentially dangling ref to `signaled`.
476
505
queue = next;
0 commit comments