150 lines
3.0 KiB
Go
150 lines
3.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// BoundedMap is a size-limited and time-limited map.
|
|
// Entries automatically expire after a TTL, and older
|
|
// entries are removed when exceeding MaxSize.
|
|
type BoundedMap struct {
|
|
mu sync.Mutex
|
|
entries map[interface{}]entry
|
|
MaxSize int
|
|
TTL time.Duration
|
|
}
|
|
|
|
type entry struct {
|
|
Value interface{}
|
|
Created time.Time
|
|
}
|
|
|
|
// NewBoundedMap creates a new bounded map with max size and TTL.
|
|
func NewBoundedMap(maxSize int, ttl time.Duration) *BoundedMap {
|
|
return &BoundedMap{
|
|
entries: make(map[interface{}]entry),
|
|
MaxSize: maxSize,
|
|
TTL: ttl,
|
|
}
|
|
}
|
|
|
|
// Set stores a key/value pair, replacing old entry if needed.
|
|
func (b *BoundedMap) Set(key, value interface{}) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
b.entries[key] = entry{
|
|
Value: value,
|
|
Created: time.Now(),
|
|
}
|
|
|
|
// If map is growing too large, evict oldest items.
|
|
if len(b.entries) > b.MaxSize {
|
|
b.evictOldest()
|
|
}
|
|
}
|
|
|
|
// Get retrieves a value if it exists and is not expired.
|
|
func (b *BoundedMap) Get(key interface{}) (interface{}, bool) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
e, ok := b.entries[key]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
if time.Since(e.Created) > b.TTL {
|
|
delete(b.entries, key)
|
|
return nil, false
|
|
}
|
|
|
|
return e.Value, true
|
|
}
|
|
|
|
// Delete removes an entry.
|
|
func (b *BoundedMap) Delete(key interface{}) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
delete(b.entries, key)
|
|
}
|
|
|
|
// CleanupOldEntries removes expired entries and returns number removed.
|
|
func (b *BoundedMap) CleanupOldEntries() int {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
count := 0
|
|
now := time.Now()
|
|
|
|
for k, e := range b.entries {
|
|
if now.Sub(e.Created) > b.TTL {
|
|
delete(b.entries, k)
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
// evictOldest removes the single oldest entry from the map.
|
|
// Called automatically when the map exceeds MaxSize.
|
|
func (b *BoundedMap) evictOldest() {
|
|
if len(b.entries) == 0 {
|
|
return
|
|
}
|
|
|
|
var (
|
|
oldestKey interface{}
|
|
oldestTS time.Time
|
|
first = true
|
|
)
|
|
|
|
for k, e := range b.entries {
|
|
if first {
|
|
oldestKey = k
|
|
oldestTS = e.Created
|
|
first = false
|
|
continue
|
|
}
|
|
if e.Created.Before(oldestTS) {
|
|
oldestKey = k
|
|
oldestTS = e.Created
|
|
}
|
|
}
|
|
|
|
delete(b.entries, oldestKey)
|
|
}
|
|
|
|
// Size returns the current number of live entries.
|
|
// Note: expired entries are not removed until Get() or CleanupOldEntries().
|
|
func (b *BoundedMap) Size() int {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return len(b.entries)
|
|
}
|
|
|
|
// Keys returns a slice of all keys (including expired ones).
|
|
// Expired keys will be filtered during normal Get/Cleanup operations.
|
|
func (b *BoundedMap) Keys() []interface{} {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
out := make([]interface{}, 0, len(b.entries))
|
|
for k := range b.entries {
|
|
out = append(out, k)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Values returns all values in the map (including expired ones).
|
|
func (b *BoundedMap) Values() []interface{} {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
out := make([]interface{}, 0, len(b.entries))
|
|
for _, e := range b.entries {
|
|
out = append(out, e.Value)
|
|
}
|
|
return out
|
|
} |