66// option. This file may not be copied, modified, or distributed
77// except according to those terms.
88
9- use core:: sync:: atomic:: { AtomicUsize , Ordering } ;
9+ use core:: sync:: atomic:: { AtomicUsize , Ordering :: Relaxed } ;
1010
1111// This structure represents a laziliy initialized static usize value. Useful
1212// when it is perferable to just rerun initialization instead of locking.
13+ // Both unsync_init and sync_init will invoke an init() function until it
14+ // succeeds, then return the cached value for future calls.
15+ //
16+ // Both methods support init() "failing". If the init() method returns UNINIT,
17+ // that value will be returned as normal, but will not be cached.
18+ //
19+ // Users should only depend on the _value_ returned by init() functions.
20+ // Specifically, for the following init() function:
21+ // fn init() -> usize {
22+ // a();
23+ // let v = b();
24+ // c();
25+ // v
26+ // }
27+ // the effects of c() or writes to shared memory will not necessarily be
28+ // observed and additional syncronization methods with be needed.
1329pub struct LazyUsize ( AtomicUsize ) ;
1430
1531impl LazyUsize {
@@ -19,20 +35,44 @@ impl LazyUsize {
1935
2036 // The initialization is not completed.
2137 pub const UNINIT : usize = usize:: max_value ( ) ;
38+ // The initialization is currently running.
39+ pub const ACTIVE : usize = usize:: max_value ( ) - 1 ;
2240
2341 // Runs the init() function at least once, returning the value of some run
24- // of init(). Unlike std::sync::Once, the init() function may be run
25- // multiple times. If init() returns UNINIT, future calls to unsync_init()
26- // will always retry. This makes UNINIT ideal for representing failure.
42+ // of init(). Multiple callers can run their init() functions in parallel.
43+ // init() should always return the same value, if it succeeds.
2744 pub fn unsync_init ( & self , init : impl FnOnce ( ) -> usize ) -> usize {
2845 // Relaxed ordering is fine, as we only have a single atomic variable.
29- let mut val = self . 0 . load ( Ordering :: Relaxed ) ;
46+ let mut val = self . 0 . load ( Relaxed ) ;
3047 if val == Self :: UNINIT {
3148 val = init ( ) ;
32- self . 0 . store ( val, Ordering :: Relaxed ) ;
49+ self . 0 . store ( val, Relaxed ) ;
3350 }
3451 val
3552 }
53+
54+ // Synchronously runs the init() function. Only one caller will have their
55+ // init() function running at a time, and exactly one successful call will
56+ // be run. The init() function should never return LazyUsize::ACTIVE.
57+ pub fn sync_init ( & self , init : impl FnOnce ( ) -> usize , mut wait : impl FnMut ( ) ) -> usize {
58+ // Common and fast path with no contention. Don't wast time on CAS.
59+ match self . 0 . load ( Relaxed ) {
60+ Self :: UNINIT | Self :: ACTIVE => { }
61+ val => return val,
62+ }
63+ // Relaxed ordering is fine, as we only have a single atomic variable.
64+ loop {
65+ match self . 0 . compare_and_swap ( Self :: UNINIT , Self :: ACTIVE , Relaxed ) {
66+ Self :: UNINIT => {
67+ let val = init ( ) ;
68+ self . 0 . store ( val, Relaxed ) ;
69+ return val;
70+ }
71+ Self :: ACTIVE => wait ( ) ,
72+ val => return val,
73+ }
74+ }
75+ }
3676}
3777
3878// Identical to LazyUsize except with bool instead of usize.
0 commit comments