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