blob: 7c6bf3c6da68a80e2d4af03b4d5d5c4f276bf736 [file] [log] [blame]
Serge Bazanskie643fd62023-02-14 00:01:38 +01001// Package sinbin implements a sinbin for naughty processed elements that we wish
2// to time out for a while. This is kept in memory, and effectively implements a
3// simplified version of the Circuit Breaker pattern.
4//
5// “sin bin”, noun, informal: (in sport) a box or bench to which offending
6// players can be sent for a period as a penalty during a game, especially in ice
7// hockey.
8package sinbin
9
10import (
11 "sync"
12 "time"
13)
14
15type entry struct {
16 until time.Time
17}
18
19// A Sinbin contains a set of entries T which are added with a deadline, and will
20// be automatically collected when that deadline expires.
21//
22// The zero value of a Sinbin is ready to use, and can be called from multiple
23// goroutines.
24type Sinbin[T comparable] struct {
25 mu sync.RWMutex
26 bench map[T]*entry
27
28 lastSweep time.Time
29}
30
31func (s *Sinbin[T]) initializeUnlocked() {
32 if s.bench == nil {
33 s.bench = make(map[T]*entry)
34 }
35}
36
37func (s *Sinbin[T]) sweepUnlocked() {
38 if s.lastSweep.Add(time.Minute).After(time.Now()) {
39 return
40 }
41 now := time.Now()
42 for k, e := range s.bench {
43 if now.After(e.until) {
44 delete(s.bench, k)
45 }
46 }
47 s.lastSweep = now
48}
49
50// Add an element 't' to a Sinbin with a given deadline. From now until that
51// deadline Penalized(t) will return true.
52func (s *Sinbin[T]) Add(t T, until time.Time) {
53 s.mu.Lock()
54 defer s.mu.Unlock()
55
56 s.initializeUnlocked()
57 s.sweepUnlocked()
58
59 existing, ok := s.bench[t]
60 if ok {
61 if until.After(existing.until) {
62 existing.until = until
63 }
64 return
65 }
66 s.bench[t] = &entry{
67 until: until,
68 }
69}
70
71// Penalized returns whether the given element is currently sitting on the
72// time-out bench after having been Added previously.
73func (s *Sinbin[T]) Penalized(t T) bool {
74 s.mu.RLock()
75 defer s.mu.RUnlock()
76
77 if s.bench == nil {
78 return false
79 }
80
81 existing, ok := s.bench[t]
82 if !ok {
83 return false
84 }
85 if time.Now().After(existing.until) {
86 delete(s.bench, t)
87 return false
88 }
89 return true
90}