36 lines
1.2 KiB
Go
36 lines
1.2 KiB
Go
package tools
|
|
|
|
import (
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// IntervalLimiter permits an action at most once per configured interval. It is
|
|
// safe for concurrent use and does no allocation or locking on the hot path -
|
|
// just an atomic load (and a single CAS when the action is actually allowed),
|
|
// so guarding a rare log line with it is effectively free when the interval has
|
|
// not elapsed.
|
|
type IntervalLimiter struct {
|
|
intervalNanos int64
|
|
lastNanos atomic.Int64
|
|
}
|
|
|
|
// NewIntervalLimiter creates an IntervalLimiter that allows an action at most
|
|
// once per interval.
|
|
func NewIntervalLimiter(interval time.Duration) *IntervalLimiter {
|
|
return &IntervalLimiter{intervalNanos: int64(interval)}
|
|
}
|
|
|
|
// Allow reports whether the action may run now. Across all concurrent callers it
|
|
// returns true at most once per interval. The first call always returns true.
|
|
func (l *IntervalLimiter) Allow() bool {
|
|
now := time.Now().UnixNano()
|
|
last := l.lastNanos.Load()
|
|
if now-last < l.intervalNanos {
|
|
return false
|
|
}
|
|
// Only the caller that swaps in the new timestamp is allowed to proceed, so
|
|
// concurrent callers in the same window can't all log.
|
|
return l.lastNanos.CompareAndSwap(last, now)
|
|
}
|