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  }