github.com/haraldrudell/parl@v0.4.176/counter/counter.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 // Package counter provides simple and rate counters and tracked datapoints 7 package counter 8 9 import ( 10 "sync/atomic" 11 12 "github.com/haraldrudell/parl" 13 ) 14 15 // Counter is a counter without rate information. Thread-safe. 16 // - provider methods: Inc Dec Add 17 // - consumer methods: Get GetReset Value Running Max 18 // - initialization-free 19 type Counter struct { 20 CounterConsumer 21 } 22 23 var _ parl.Counter = &Counter{} // Counter supports data providers 24 var _ parl.CounterValues = &Counter{} // Counter supports data consumers 25 26 // newCounter returns a counter without rate information. 27 func newCounter() (counter parl.Counter) { // Counter is parl.Counter 28 return &Counter{} 29 } 30 31 // Inc increments the counter. Thread-Safe, method chaining 32 func (c *Counter) Inc() (counter parl.Counter) { 33 counter = c 34 // acquire and relinquish lock or atomic, lock-free access 35 defer c.a.RelinquishAccess(c.a.RequestAccess()) 36 37 // update value, running and max 38 atomic.AddUint64(&c.value, 1) 39 c.updateMax(atomic.AddUint64(&c.running, 1)) // returns the new value 40 41 return 42 } 43 44 // Dec decrements the counter but not below zero. Thread-Safe, method chaining 45 func (c *Counter) Dec() (counter parl.Counter) { 46 counter = c 47 // acquire and relinquish lock or atomic, lock-free access 48 defer c.a.RelinquishAccess(c.a.RequestAccess()) 49 50 for { 51 52 // check if running can be decremented 53 var running = atomic.LoadUint64(&c.running) // current value 54 if running == 0 { 55 return // do not decrement lower than 0 return 56 } 57 58 // attempt to decrement 59 var newRunning = running - 1 // 0… 60 if atomic.CompareAndSwapUint64(&c.running, running, newRunning) { 61 return // decrement succeeded return 62 } 63 } 64 } 65 66 // Add adds a positive or negative delta. Thread-Safe, method chaining 67 func (c *Counter) Add(delta int64) (counter parl.Counter) { 68 counter = c 69 // check for nothing to do 70 if delta == 0 { 71 return // no change return 72 } 73 // acquire and relinquish lock or atomic, lock-free access 74 defer c.a.RelinquishAccess(c.a.RequestAccess()) 75 76 // positive case: update value, running, max 77 if delta > 0 { 78 var deltaU64 = uint64(delta) 79 atomic.AddUint64(&c.value, deltaU64) 80 c.updateMax(atomic.AddUint64(&c.running, deltaU64)) 81 return // popsitive addition complete return 82 } 83 84 // delta negative 85 var decrementAmount = uint64(-delta) 86 for { 87 88 // cap decrement amount to avoid negative result 89 var running = atomic.LoadUint64(&c.running) 90 if decrementAmount > running { 91 decrementAmount = running 92 } 93 94 // check for nothing to do 95 if decrementAmount == 0 { 96 return // cannot decrement return 97 } 98 99 // attempt to store new value 100 if atomic.CompareAndSwapUint64(&c.running, running, running-decrementAmount) { 101 return // decrement successful return 102 } 103 } 104 } 105 106 // Consumer return the read-only consumer interface for this counter 107 func (c *Counter) Consumer() (consumer parl.CounterValues) { 108 return &c.CounterConsumer 109 } 110 111 // updateMax ensures that max is at least running 112 func (c *Counter) updateMax(running uint64) { 113 for { 114 115 // check whether max has acceptable value 116 var max = atomic.LoadUint64(&c.max) // get current max value 117 if running <= max { 118 return // no update required return 119 } 120 121 // CompareAndSwapUint64 updates to running if value still matches max 122 if atomic.CompareAndSwapUint64(&c.max, max, running) { 123 return // update successful return 124 } 125 } 126 }