| // Package sinbin implements a sinbin for naughty processed elements that we wish |
| // to time out for a while. This is kept in memory, and effectively implements a |
| // simplified version of the Circuit Breaker pattern. |
| // |
| // “sin bin”, noun, informal: (in sport) a box or bench to which offending |
| // players can be sent for a period as a penalty during a game, especially in ice |
| // hockey. |
| package sinbin |
| |
| import ( |
| "sync" |
| "time" |
| ) |
| |
| type entry struct { |
| until time.Time |
| } |
| |
| // A Sinbin contains a set of entries T which are added with a deadline, and will |
| // be automatically collected when that deadline expires. |
| // |
| // The zero value of a Sinbin is ready to use, and can be called from multiple |
| // goroutines. |
| type Sinbin[T comparable] struct { |
| mu sync.RWMutex |
| bench map[T]*entry |
| |
| lastSweep time.Time |
| } |
| |
| func (s *Sinbin[T]) initializeUnlocked() { |
| if s.bench == nil { |
| s.bench = make(map[T]*entry) |
| } |
| } |
| |
| func (s *Sinbin[T]) sweepUnlocked() { |
| if s.lastSweep.Add(time.Minute).After(time.Now()) { |
| return |
| } |
| now := time.Now() |
| for k, e := range s.bench { |
| if now.After(e.until) { |
| delete(s.bench, k) |
| } |
| } |
| s.lastSweep = now |
| } |
| |
| // Add an element 't' to a Sinbin with a given deadline. From now until that |
| // deadline Penalized(t) will return true. |
| func (s *Sinbin[T]) Add(t T, until time.Time) { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| |
| s.initializeUnlocked() |
| s.sweepUnlocked() |
| |
| existing, ok := s.bench[t] |
| if ok { |
| if until.After(existing.until) { |
| existing.until = until |
| } |
| return |
| } |
| s.bench[t] = &entry{ |
| until: until, |
| } |
| } |
| |
| // Penalized returns whether the given element is currently sitting on the |
| // time-out bench after having been Added previously. |
| func (s *Sinbin[T]) Penalized(t T) bool { |
| s.mu.RLock() |
| defer s.mu.RUnlock() |
| |
| if s.bench == nil { |
| return false |
| } |
| |
| existing, ok := s.bench[t] |
| if !ok { |
| return false |
| } |
| if time.Now().After(existing.until) { |
| delete(s.bench, t) |
| return false |
| } |
| return true |
| } |