Weighs the soul of incoming HTTP requests to stop AI crawlers
at main 127 lines 2.5 kB view raw
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}