Files
Sneedchat-Matrix-Bridge-Go/utils/boundedmap.go
2025-11-18 02:07:08 -05:00

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
}