initial commit
This commit is contained in:
commit
0b9942832a
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module git.tordarus.net/tordarus/cachemap
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require git.tordarus.net/tordarus/cmap v0.0.5
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
git.tordarus.net/tordarus/cmap v0.0.5 h1:g2STu3iMXCLn96JZ/f0iN5hbzct9cI5B8+V8ATB4ryE=
|
||||
git.tordarus.net/tordarus/cmap v0.0.5/go.mod h1:Wx2snnIzdDrmONR9X5bt5aFq8E8EDCK5Zy50DmRwK1M=
|
137
map.go
Normal file
137
map.go
Normal file
@ -0,0 +1,137 @@
|
||||
package cachemap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"time"
|
||||
|
||||
"git.tordarus.net/tordarus/cmap"
|
||||
)
|
||||
|
||||
// CacheMap represents a map for caching values for a specific amount of time.
|
||||
// CacheMap is thread-safe.
|
||||
type CacheMap[K comparable, V any] struct {
|
||||
data *cmap.Map[K, cacheMapEntry[V]]
|
||||
defaultCacheDuration time.Duration
|
||||
autoCleanUpEnabled bool
|
||||
}
|
||||
|
||||
// New returns a CacheMap which caches its items for the given cache duration (if not specified otherwise).
|
||||
// It does not collect its own garbage until Get() or CleanUp() is called
|
||||
func New[K comparable, V any](defaultCacheDuration time.Duration) *CacheMap[K, V] {
|
||||
return &CacheMap[K, V]{
|
||||
data: cmap.New[K, cacheMapEntry[V]](),
|
||||
defaultCacheDuration: defaultCacheDuration,
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a CacheMap which caches its items for the given cache duration (if not specified otherwise).
|
||||
// It collects its own garbage periodically. Memory leaks are not possible but the map is locked for the cleanup process
|
||||
func NewSelfCleaning[K comparable, V any](ctx context.Context, defaultCacheDuration, cleanUpInterval time.Duration) *CacheMap[K, V] {
|
||||
cacheMap := New[K, V](defaultCacheDuration)
|
||||
cacheMap.AutoCleanUp(ctx, cleanUpInterval)
|
||||
return cacheMap
|
||||
}
|
||||
|
||||
// Put puts the given key-value pair into the cache map with the CacheMaps default cache duration
|
||||
func (cm *CacheMap[K, V]) Put(key K, value V) {
|
||||
cm.data.Put(key, cacheMapEntry[V]{
|
||||
Value: value,
|
||||
CacheTime: time.Now(),
|
||||
CacheDuration: cm.defaultCacheDuration,
|
||||
})
|
||||
}
|
||||
|
||||
// Put puts the given key-value pair into the cache map with the given cache duration
|
||||
func (cm *CacheMap[K, V]) PutFor(key K, value V, cacheDuration time.Duration) {
|
||||
cm.data.Put(key, cacheMapEntry[V]{
|
||||
Value: value,
|
||||
CacheTime: time.Now(),
|
||||
CacheDuration: cacheDuration,
|
||||
})
|
||||
}
|
||||
|
||||
// Get returns the value associated with the given key and a boolean indicating an existing entry.
|
||||
// If the entry associated with the key is expired, the key is deleted from the map immediately.
|
||||
// KeepAlive is called for the entry if it is not expired yet.
|
||||
func (cm *CacheMap[K, V]) Get(key K) (V, bool) {
|
||||
entry, ok := cm.data.GetHas(key)
|
||||
if !ok {
|
||||
return *new(V), false
|
||||
}
|
||||
|
||||
if !entry.IsAlive() {
|
||||
cm.data.Delete(key)
|
||||
return *new(V), false
|
||||
}
|
||||
|
||||
cm.KeepAlive(key)
|
||||
return entry.Value, true
|
||||
}
|
||||
|
||||
// KeepAlive restarts the cache duration for the entry associated with the given key.
|
||||
// This is the same as deleting and re-adding the entry with the same cache duration.
|
||||
func (cm *CacheMap[K, V]) KeepAlive(key K) {
|
||||
entry, ok := cm.data.GetHas(key)
|
||||
if ok {
|
||||
entry.CacheTime = time.Now()
|
||||
cm.data.Put(key, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate returns an iterator for the CacheMap to use in for-range loops
|
||||
func (cm *CacheMap[K, V]) Iterate() iter.Seq2[K, V] {
|
||||
return func(yield func(K, V) bool) {
|
||||
for key, value := range cm.data.Iterate() {
|
||||
if !yield(key, value.Value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CleanUp iterates through the CacheMap and deletes all expired entries.
|
||||
// The CacheMap is locked during the cleanup process.
|
||||
func (cm *CacheMap[K, V]) CleanUp() {
|
||||
cm.data.Do(func(m map[K]cacheMapEntry[V]) {
|
||||
for key, entry := range m {
|
||||
if !entry.IsAlive() {
|
||||
delete(m, key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AutoCleanUp periodically calls CleanUp() to ensure that no garbage is accumulating
|
||||
// over long periods of time without Get calls.
|
||||
func (cm *CacheMap[K, V]) AutoCleanUp(ctx context.Context, interval time.Duration) {
|
||||
if cm.autoCleanUpEnabled {
|
||||
return
|
||||
}
|
||||
cm.autoCleanUpEnabled = true
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
defer func() { cm.autoCleanUpEnabled = false }()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
cm.CleanUp()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type cacheMapEntry[T any] struct {
|
||||
Value T
|
||||
CacheTime time.Time
|
||||
CacheDuration time.Duration
|
||||
}
|
||||
|
||||
func (e cacheMapEntry[T]) IsAlive() bool {
|
||||
return time.Since(e.CacheTime) < e.CacheDuration
|
||||
}
|
222
utils.go
Normal file
222
utils.go
Normal file
@ -0,0 +1,222 @@
|
||||
package cachemap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CacheFunc11[K1 comparable, V1 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1) V1) func(K1) V1 {
|
||||
cacheMap := NewSelfCleaning[K1, V1](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key K1) V1 {
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value
|
||||
}
|
||||
|
||||
value := f(key)
|
||||
cacheMap.Put(key, value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc12[K1 comparable, V1, V2 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1) (V1, V2)) func(K1) (V1, V2) {
|
||||
type Value struct {
|
||||
Value1 V1
|
||||
Value2 V2
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[K1, Value](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key K1) (V1, V2) {
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value.Value1, value.Value2
|
||||
}
|
||||
|
||||
value1, value2 := f(key)
|
||||
cacheMap.Put(key, Value{
|
||||
Value1: value1,
|
||||
Value2: value2,
|
||||
})
|
||||
return value1, value2
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc13[K1 comparable, V1, V2, V3 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1) (V1, V2, V3)) func(K1) (V1, V2, V3) {
|
||||
type Value struct {
|
||||
Value1 V1
|
||||
Value2 V2
|
||||
Value3 V3
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[K1, Value](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key K1) (V1, V2, V3) {
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value.Value1, value.Value2, value.Value3
|
||||
}
|
||||
|
||||
value1, value2, value3 := f(key)
|
||||
cacheMap.Put(key, Value{
|
||||
Value1: value1,
|
||||
Value2: value2,
|
||||
Value3: value3,
|
||||
})
|
||||
return value1, value2, value3
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc21[K1, K2 comparable, V1 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1, K2) V1) func(K1, K2) V1 {
|
||||
type Key struct {
|
||||
Key1 K1
|
||||
Key2 K2
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[Key, V1](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key1 K1, key2 K2) V1 {
|
||||
key := Key{Key1: key1, Key2: key2}
|
||||
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value
|
||||
}
|
||||
|
||||
value := f(key1, key2)
|
||||
cacheMap.Put(key, value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc22[K1, K2 comparable, V1, V2 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1, K2) (V1, V2)) func(K1, K2) (V1, V2) {
|
||||
type Key struct {
|
||||
Key1 K1
|
||||
Key2 K2
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Value1 V1
|
||||
Value2 V2
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[Key, Value](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key1 K1, key2 K2) (V1, V2) {
|
||||
key := Key{Key1: key1, Key2: key2}
|
||||
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value.Value1, value.Value2
|
||||
}
|
||||
|
||||
value1, value2 := f(key1, key2)
|
||||
cacheMap.Put(key, Value{
|
||||
Value1: value1,
|
||||
Value2: value2,
|
||||
})
|
||||
return value1, value2
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc23[K1, K2 comparable, V1, V2, V3 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1, K2) (V1, V2, V3)) func(K1, K2) (V1, V2, V3) {
|
||||
type Key struct {
|
||||
Key1 K1
|
||||
Key2 K2
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Value1 V1
|
||||
Value2 V2
|
||||
Value3 V3
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[Key, Value](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key1 K1, key2 K2) (V1, V2, V3) {
|
||||
key := Key{Key1: key1, Key2: key2}
|
||||
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value.Value1, value.Value2, value.Value3
|
||||
}
|
||||
|
||||
value1, value2, value3 := f(key1, key2)
|
||||
cacheMap.Put(key, Value{
|
||||
Value1: value1,
|
||||
Value2: value2,
|
||||
Value3: value3,
|
||||
})
|
||||
return value1, value2, value3
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc31[K1, K2, K3 comparable, V1 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1, K2, K3) V1) func(K1, K2, K3) V1 {
|
||||
type Key struct {
|
||||
Key1 K1
|
||||
Key2 K2
|
||||
Key3 K3
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[Key, V1](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key1 K1, key2 K2, key3 K3) V1 {
|
||||
key := Key{Key1: key1, Key2: key2, Key3: key3}
|
||||
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value
|
||||
}
|
||||
|
||||
value := f(key1, key2, key3)
|
||||
cacheMap.Put(key, value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc32[K1, K2, K3 comparable, V1, V2 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1, K2, K3) (V1, V2)) func(K1, K2, K3) (V1, V2) {
|
||||
type Key struct {
|
||||
Key1 K1
|
||||
Key2 K2
|
||||
Key3 K3
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Value1 V1
|
||||
Value2 V2
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[Key, Value](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key1 K1, key2 K2, key3 K3) (V1, V2) {
|
||||
key := Key{Key1: key1, Key2: key2}
|
||||
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value.Value1, value.Value2
|
||||
}
|
||||
|
||||
value1, value2 := f(key1, key2, key3)
|
||||
cacheMap.Put(key, Value{
|
||||
Value1: value1,
|
||||
Value2: value2,
|
||||
})
|
||||
return value1, value2
|
||||
}
|
||||
}
|
||||
|
||||
func CacheFunc33[K1, K2, K3 comparable, V1, V2, V3 any](ctx context.Context, cacheDuration, cleanUpInterval time.Duration, f func(K1, K2, K3) (V1, V2, V3)) func(K1, K2, K3) (V1, V2, V3) {
|
||||
type Key struct {
|
||||
Key1 K1
|
||||
Key2 K2
|
||||
Key3 K3
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Value1 V1
|
||||
Value2 V2
|
||||
Value3 V3
|
||||
}
|
||||
|
||||
cacheMap := NewSelfCleaning[Key, Value](ctx, cacheDuration, cleanUpInterval)
|
||||
return func(key1 K1, key2 K2, key3 K3) (V1, V2, V3) {
|
||||
key := Key{Key1: key1, Key2: key2}
|
||||
|
||||
if value, ok := cacheMap.Get(key); ok {
|
||||
return value.Value1, value.Value2, value.Value3
|
||||
}
|
||||
|
||||
value1, value2, value3 := f(key1, key2, key3)
|
||||
cacheMap.Put(key, Value{
|
||||
Value1: value1,
|
||||
Value2: value2,
|
||||
Value3: value3,
|
||||
})
|
||||
return value1, value2, value3
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user