github.com/xmidt-org/webpa-common@v1.11.9/capacitor/capacitor.go (about) 1 package capacitor 2 3 import ( 4 "sync" 5 "sync/atomic" 6 "time" 7 8 "github.com/xmidt-org/webpa-common/clock" 9 ) 10 11 // DefaultDelay is the default time a capacitor waits to execute the most recently 12 // submitted function 13 const DefaultDelay time.Duration = time.Second 14 15 // Interface represents a capacitor of function calls which will discharge after 16 // a configurable period of time. 17 type Interface interface { 18 // Submit submits a function for execution. The function will not be executed immediately. 19 // Instead, after a configurable period of time, the most recent function passed to Submit will 20 // be executed. The previous ones are ignored. 21 Submit(func()) 22 23 // Discharge forcibly discharges this capacitor. The most recent function passed to Submit is 24 // executed, and the internal state is reset so that the next call to Submit will start the 25 // process of delaying function calls all over again. 26 Discharge() 27 28 // Cancel terminates any waiting function call without executing it. As with Discharge, the 29 // internal state is reset so that Submit calls will delay functions as normal again. 30 Cancel() 31 } 32 33 // Option represents a configuration option for a capacitor 34 type Option func(*capacitor) 35 36 func WithDelay(d time.Duration) Option { 37 return func(c *capacitor) { 38 if d > 0 { 39 c.delay = d 40 } else { 41 c.delay = DefaultDelay 42 } 43 } 44 } 45 46 func WithClock(cl clock.Interface) Option { 47 return func(c *capacitor) { 48 if cl != nil { 49 c.c = cl 50 } else { 51 c.c = clock.System() 52 } 53 } 54 } 55 56 // New creates a capacitor with the given options. 57 func New(o ...Option) Interface { 58 c := &capacitor{ 59 delay: DefaultDelay, 60 c: clock.System(), 61 } 62 63 for _, f := range o { 64 f(c) 65 } 66 67 return c 68 } 69 70 // delayer is the internal job type that holds the context for a single, delayed 71 // charge of the capacitor 72 type delayer struct { 73 current atomic.Value 74 timer <-chan time.Time 75 terminate chan bool 76 cleanup func() 77 } 78 79 func (d *delayer) discharge() { 80 d.terminate <- true 81 } 82 83 func (d *delayer) cancel() { 84 d.terminate <- false 85 } 86 87 func (d *delayer) execute() { 88 if f, ok := d.current.Load().(func()); f != nil && ok { 89 f() 90 } 91 } 92 93 // run is called as a goroutine and will exit when either the timer channel 94 // is signalled or the terminate channel receives a value. 95 func (d *delayer) run() { 96 defer d.cleanup() 97 98 select { 99 case <-d.timer: 100 // since the timer can fire at the same time as Discharge or Cancel, 101 // we want to make sure that any type of explicit termination trumps the timer 102 select { 103 case discharge := <-d.terminate: 104 if discharge { 105 d.execute() 106 } 107 108 default: 109 d.execute() 110 } 111 112 case discharge := <-d.terminate: 113 if discharge { 114 d.execute() 115 } 116 } 117 } 118 119 // capacitor implements Interface, and provides an atomically updated delayer job 120 type capacitor struct { 121 lock sync.Mutex 122 delay time.Duration 123 c clock.Interface 124 d *delayer 125 } 126 127 func (c *capacitor) Submit(v func()) { 128 c.lock.Lock() 129 if c.d == nil { 130 var ( 131 t = c.c.NewTimer(c.delay) 132 d = &delayer{ 133 terminate: make(chan bool, 1), 134 timer: t.C(), 135 } 136 ) 137 138 d.current.Store(v) 139 140 // create a cleanup closure that stops the timer and 141 // ensures that the given delayer is cleared, allowing 142 // for barging. 143 d.cleanup = func() { 144 t.Stop() 145 c.lock.Lock() 146 if c.d == d { 147 c.d = nil 148 } 149 c.lock.Unlock() 150 } 151 152 c.d = d 153 go c.d.run() 154 } else { 155 c.d.current.Store(v) 156 } 157 158 c.lock.Unlock() 159 } 160 161 func (c *capacitor) Discharge() { 162 c.lock.Lock() 163 if c.d != nil { 164 c.d.discharge() 165 c.d = nil 166 } 167 168 c.lock.Unlock() 169 } 170 171 func (c *capacitor) Cancel() { 172 c.lock.Lock() 173 if c.d != nil { 174 c.d.cancel() 175 c.d = nil 176 } 177 178 c.lock.Unlock() 179 }