Weighs the soul of incoming HTTP requests to stop AI crawlers
1package decaymap
2
3import (
4 "sync"
5 "time"
6)
7
8func Zilch[T any]() T {
9 var zero T
10 return zero
11}
12
13// Impl is a lazy key->value map. It's a wrapper around a map and a mutex. If values exceed their time-to-live, they are pruned at Get time.
14type Impl[K comparable, V any] struct {
15 data map[K]decayMapEntry[V]
16 lock sync.RWMutex
17}
18
19type decayMapEntry[V any] struct {
20 Value V
21 expiry time.Time
22}
23
24// New creates a new DecayMap of key type K and value type V.
25//
26// Key types must be comparable to work with maps.
27func New[K comparable, V any]() *Impl[K, V] {
28 return &Impl[K, V]{
29 data: make(map[K]decayMapEntry[V]),
30 }
31}
32
33// expire forcibly expires a key by setting its time-to-live one second in the past.
34func (m *Impl[K, V]) expire(key K) bool {
35 m.lock.RLock()
36 val, ok := m.data[key]
37 m.lock.RUnlock()
38
39 if !ok {
40 return false
41 }
42
43 m.lock.Lock()
44 val.expiry = time.Now().Add(-1 * time.Second)
45 m.data[key] = val
46 m.lock.Unlock()
47
48 return true
49}
50
51// Delete a value from the DecayMap by key.
52//
53// If the value does not exist, return false. Return true after
54// deletion.
55func (m *Impl[K, V]) Delete(key K) bool {
56 m.lock.RLock()
57 _, ok := m.data[key]
58 m.lock.RUnlock()
59
60 if !ok {
61 return false
62 }
63
64 m.lock.Lock()
65 delete(m.data, key)
66 m.lock.Unlock()
67
68 return true
69}
70
71// Get gets a value from the DecayMap by key.
72//
73// If a value has expired, forcibly delete it if it was not updated.
74func (m *Impl[K, V]) Get(key K) (V, bool) {
75 m.lock.RLock()
76 value, ok := m.data[key]
77 m.lock.RUnlock()
78
79 if !ok {
80 return Zilch[V](), false
81 }
82
83 if time.Now().After(value.expiry) {
84 m.lock.Lock()
85 // Since previously reading m.data[key], the value may have been updated.
86 // Delete the entry only if the expiry time is still the same.
87 if m.data[key].expiry.Equal(value.expiry) {
88 delete(m.data, key)
89 }
90 m.lock.Unlock()
91
92 return Zilch[V](), false
93 }
94
95 return value.Value, true
96}
97
98// Set sets a key value pair in the map.
99func (m *Impl[K, V]) Set(key K, value V, ttl time.Duration) {
100 m.lock.Lock()
101 defer m.lock.Unlock()
102
103 m.data[key] = decayMapEntry[V]{
104 Value: value,
105 expiry: time.Now().Add(ttl),
106 }
107}
108
109// Cleanup removes all expired entries from the DecayMap.
110func (m *Impl[K, V]) Cleanup() {
111 m.lock.Lock()
112 defer m.lock.Unlock()
113
114 now := time.Now()
115 for key, entry := range m.data {
116 if now.After(entry.expiry) {
117 delete(m.data, key)
118 }
119 }
120}
121
122// Len returns the number of entries in the DecayMap.
123func (m *Impl[K, V]) Len() int {
124 m.lock.RLock()
125 defer m.lock.RUnlock()
126 return len(m.data)
127}