github.com/safing/portbase@v0.19.5/utils/onceagain.go (about) 1 package utils 2 3 // This file is forked from https://github.com/golang/go/blob/bc593eac2dc63d979a575eccb16c7369a5ff81e0/src/sync/once.go. 4 5 import ( 6 "sync" 7 "sync/atomic" 8 ) 9 10 // OnceAgain is an object that will perform only one action "in flight". It's 11 // basically the same as sync.Once, but is automatically reused when the 12 // function was executed and everyone who waited has left. 13 // Important: This is somewhat racy when used heavily as it only resets _after_ 14 // everyone who waited has left. So, while some goroutines are waiting to be 15 // activated again to leave the waiting state, other goroutines will call Do() 16 // without executing the function again. 17 type OnceAgain struct { 18 // done indicates whether the action has been performed. 19 // It is first in the struct because it is used in the hot path. 20 // The hot path is inlined at every call site. 21 // Placing done first allows more compact instructions on some architectures (amd64/x86), 22 // and fewer instructions (to calculate offset) on other architectures. 23 done uint32 24 25 // Number of waiters waiting for the function to finish. The last waiter resets done. 26 waiters int32 27 28 m sync.Mutex 29 } 30 31 // Do calls the function f if and only if Do is being called for the 32 // first time for this instance of Once. In other words, given 33 // 34 // var once Once 35 // 36 // if once.Do(f) is called multiple times, only the first call will invoke f, 37 // even if f has a different value in each invocation. A new instance of 38 // Once is required for each function to execute. 39 // 40 // Do is intended for initialization that must be run exactly once. Since f 41 // is niladic, it may be necessary to use a function literal to capture the 42 // arguments to a function to be invoked by Do: 43 // 44 // config.once.Do(func() { config.init(filename) }) 45 // 46 // Because no call to Do returns until the one call to f returns, if f causes 47 // Do to be called, it will deadlock. 48 // 49 // If f panics, Do considers it to have returned; future calls of Do return 50 // without calling f. 51 func (o *OnceAgain) Do(f func()) { 52 // Note: Here is an incorrect implementation of Do: 53 // 54 // if atomic.CompareAndSwapUint32(&o.done, 0, 1) { 55 // f() 56 // } 57 // 58 // Do guarantees that when it returns, f has finished. 59 // This implementation would not implement that guarantee: 60 // given two simultaneous calls, the winner of the cas would 61 // call f, and the second would return immediately, without 62 // waiting for the first's call to f to complete. 63 // This is why the slow path falls back to a mutex, and why 64 // the atomic.StoreUint32 must be delayed until after f returns. 65 66 if atomic.LoadUint32(&o.done) == 0 { 67 // Outlined slow-path to allow inlining of the fast-path. 68 o.doSlow(f) 69 } 70 } 71 72 func (o *OnceAgain) doSlow(f func()) { 73 atomic.AddInt32(&o.waiters, 1) 74 defer func() { 75 if atomic.AddInt32(&o.waiters, -1) == 0 { 76 atomic.StoreUint32(&o.done, 0) // reset 77 } 78 }() 79 80 o.m.Lock() 81 defer o.m.Unlock() 82 if o.done == 0 { 83 defer atomic.StoreUint32(&o.done, 1) 84 f() 85 } 86 }