| // 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 | 
 | } |