diff --git a/README.md b/README.md index 29a167b..e1f341f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ few enhancements and creature-comforts: - Meaning that both key and value are determined by Generics - Using `any` or `interface{}` as the value type will effectively emulate the original source package -- `Map` accepts a [`context.Context`](https://pkg.go.dev/context@go1.21.4) +- `Map` can be created with a [`context.Context`](https://pkg.go.dev/context@go1.21.4) - `Map` will automatically stop pruning expired items (equivalent to `Map.Close()`) if the context cancels to prevent goroutine leaks - Great for services @@ -28,6 +28,8 @@ few enhancements and creature-comforts: - The syntax is a little more idiomatic - Methods have been renamed to be more familiar to Go standard library users - `Load()` and `Store()` instead of `Get()` and `Set()` +- Key/value pairs can use the default TTL for the `Map`, or have their own individual TTL by + using `StoreWithTTL` - Code is a little safer for concurrent use (at the time of the fork) and more performant in that use case - Use of `sync.RWLock` so that read-heavy applications block less @@ -64,19 +66,14 @@ import ( ) func main() { - maxTTL := 300 * time.Millisecond // a key's time to live + defaultTTL := 300 * time.Millisecond // a key's time to live startSize := 3 // initial number of items in map pruneInterval := 100 * time.Millisecond // prune expired items each time pruneInterval elapses - refreshOnStore := true // update item's 'lastAccessTime' on ttl.Map.Load() + refreshOnLoad := true // update item's 'lastAccessTime' on ttl.Map.Load() // Any comparable data type such as int, uint64, pointers and struct types (if all field // types are comparable) can be used as the key type - t := ttl.NewMap[string, string]( - context.Background(), - maxTTL, - startSize, - pruneInterval, - refreshOnStore) + t := ttl.NewMap[string, string](defaultTTL, startSize, pruneInterval, refreshOnLoad) defer t.Close() // Populate the ttl.Map @@ -93,7 +90,7 @@ func main() { return true }) - sleepTime := maxTTL + pruneInterval + sleepTime := defaultTTL + pruneInterval fmt.Printf("Sleeping %s, items should be expired and removed afterward\n", sleepTime) time.Sleep(sleepTime) diff --git a/Taskfile.dist.yml b/Taskfile.dist.yml index 86dfc64..9bf3570 100644 --- a/Taskfile.dist.yml +++ b/Taskfile.dist.yml @@ -7,7 +7,7 @@ output: 'prefixed' vars: PKG_NAME: github.com/glenvan/ttl - PKG_VERSION: v1.0.0 + PKG_VERSION: v2.0.0 tasks: default: diff --git a/example/example.go b/example/example.go index 37eba7b..8d76a2a 100644 --- a/example/example.go +++ b/example/example.go @@ -1,7 +1,6 @@ package main import ( - "context" "fmt" "time" @@ -9,16 +8,12 @@ import ( ) func main() { - maxTTL := time.Duration(time.Second * 4) // time in seconds + defaultTTL := time.Duration(time.Second * 4) // time in seconds startSize := 3 // initial number of items in map pruneInterval := time.Duration(time.Second * 1) // search for expired items every 'pruneInterval' seconds - refreshOnStore := true // update item's lastAccessTime on a .Get() - t := ttl.NewMap[string, any]( - context.Background(), - maxTTL, - startSize, - pruneInterval, - refreshOnStore) + refreshOnLoad := true // update item's lastAccessTime on a .Get() + + t := ttl.NewMap[string, any](defaultTTL, startSize, pruneInterval, refreshOnLoad) defer t.Close() // populate the ttl.Map @@ -54,7 +49,7 @@ func main() { }() // ttl.Map has an expiration time, wait until this amount of time passes - sleepTime := maxTTL + 2*pruneInterval + sleepTime := defaultTTL + 2*pruneInterval fmt.Println() fmt.Printf( "Sleeping %v, items should be removed after this time, except for the '%v' key\n", diff --git a/example/small/small.go b/example/small/small.go index 9ae1348..1412214 100644 --- a/example/small/small.go +++ b/example/small/small.go @@ -1,7 +1,6 @@ package main import ( - "context" "fmt" "time" @@ -9,19 +8,14 @@ import ( ) func main() { - maxTTL := 300 * time.Millisecond // a key's time to live + defaultTTL := 300 * time.Millisecond // a key's time to live startSize := 3 // initial number of items in map pruneInterval := 100 * time.Millisecond // prune expired items each time pruneInterval elapses - refreshOnStore := true // update item's 'lastAccessTime' on ttl.Map.Load() + refreshOnLoad := true // update item's 'lastAccessTime' on ttl.Map.Load() // Any comparable data type such as int, uint64, pointers and struct types (if all field // types are comparable) can be used as the key type - t := ttl.NewMap[string, string]( - context.Background(), - maxTTL, - startSize, - pruneInterval, - refreshOnStore) + t := ttl.NewMap[string, string](defaultTTL, startSize, pruneInterval, refreshOnLoad) defer t.Close() // Populate the ttl.Map @@ -38,7 +32,7 @@ func main() { return true }) - sleepTime := maxTTL + pruneInterval + sleepTime := defaultTTL + pruneInterval fmt.Printf("Sleeping %s, items should be expired and removed afterward\n", sleepTime) time.Sleep(sleepTime) diff --git a/example_test.go b/example_test.go index ad45bdf..3e0d0c8 100644 --- a/example_test.go +++ b/example_test.go @@ -1,7 +1,6 @@ package ttl_test import ( - "context" "fmt" "time" @@ -9,19 +8,14 @@ import ( ) func ExampleMap() { - maxTTL := 300 * time.Millisecond // a key's time to live + defaultTTL := 300 * time.Millisecond // the default lifetime for a key/value pair startSize := 3 // initial number of items in map pruneInterval := 100 * time.Millisecond // prune expired items each time pruneInterval elapses - refreshLastAccessOnGet := true // update item's 'lastAccessTime' on ttl.Map.Load() + refreshOnLoad := true // update item's 'lastAccessTime' on ttl.Map.Load() // Any comparable data type such as int, uint64, pointers and struct types (if all field // types are comparable) can be used as the key type - t := ttl.NewMap[string, string]( - context.Background(), - maxTTL, - startSize, - pruneInterval, - refreshLastAccessOnGet) + t := ttl.NewMap[string, string](defaultTTL, startSize, pruneInterval, refreshOnLoad) defer t.Close() // Populate the ttl.Map @@ -38,7 +32,7 @@ func ExampleMap() { return true }) - sleepTime := maxTTL + pruneInterval + sleepTime := defaultTTL + pruneInterval fmt.Printf("Sleeping %s, items should be expired and removed afterward\n", sleepTime) time.Sleep(sleepTime) @@ -60,7 +54,7 @@ func ExampleMap() { } func ExampleMap_Load() { - tm := ttl.NewMap[string, string](context.Background(), 30*time.Second, 0, 2*time.Second, true) + tm := ttl.NewMap[string, string](30*time.Second, 0, 2*time.Second, true) defer tm.Close() tm.Store("hello", "world") @@ -74,7 +68,7 @@ func ExampleMap_Load() { } func ExampleMap_Range() { - tm := ttl.NewMap[string, string](context.Background(), 30*time.Second, 0, 2*time.Second, true) + tm := ttl.NewMap[string, string](30*time.Second, 0, 2*time.Second, true) defer tm.Close() tm.Store("hello", "world") @@ -95,6 +89,7 @@ func ExampleMap_Range() { return true // continue }) + // Give the goroutine some time to complete time.Sleep(20 * time.Millisecond) fmt.Printf("Length after: %d\n", tm.Length()) @@ -104,7 +99,7 @@ func ExampleMap_Range() { } func ExampleMap_DeleteFunc() { - tm := ttl.NewMap[string, int](context.Background(), 30*time.Second, 0, 2*time.Second, true) + tm := ttl.NewMap[string, int](30*time.Second, 0, 2*time.Second, true) defer tm.Close() tm.Store("zero", 0) diff --git a/map.go b/map.go index 29cf174..228932e 100644 --- a/map.go +++ b/map.go @@ -9,7 +9,8 @@ import ( ) type mapItem[V any] struct { - Value V + value V + itemTTL time.Duration lastAccess atomic.Int64 } @@ -28,37 +29,57 @@ func (i *mapItem[V]) touch() { // // Adapted from: https://stackoverflow.com/a/25487392/452281 type Map[K comparable, V any] struct { - m map[K]*mapItem[V] - mtx sync.RWMutex - refresh bool - stop chan bool - closed atomic.Bool + m map[K]*mapItem[V] + mtx sync.RWMutex + defaultTTL time.Duration + refreshOnLoad bool + stop chan bool + closed atomic.Bool } -// NewMap returns a new [Map] with items expiring according to the maxTTL specified if +// NewMap returns a new [Map] with items expiring according to the defaultTTL specified if // they have not been accessed within that duration. Access refresh can be overridden so that // items expire after the TTL whether they have been accessed or not. // -// NewMap accepts a context. If the context is cancelled, the pruning process will automatically +// [Map] objects returned by NewMap must be closed with [Map.Close] when they're no longer needed. +func NewMap[K comparable, V any]( + defaultTTL time.Duration, + length int, + pruneInterval time.Duration, + refreshOnLoad bool, +) (m *Map[K, V]) { + ctx := context.Background() + return NewMapContext[K, V](ctx, defaultTTL, length, pruneInterval, refreshOnLoad) +} + +// NewMapContext returns a new [Map] with items expiring according to the defaultTTL specified if +// they have not been accessed within that duration. Access refresh can be overridden so that +// items expire after the TTL whether they have been accessed or not. +// +// NewMapContext accepts a context. If the context is cancelled, the pruning process will automatically // stop whether you've called [Map.Close] or not. It's safe to use either approach. // // context.Background() is perfectly acceptable as the default context, however you should // [Map.Close] the Map yourself in that case. -func NewMap[K comparable, V any]( +// +// [Map] objects returned by NewMapContext may still be closed with [Map.Close] when they're no +// longer needed or if the cancellation of the context is not guaranteed. +func NewMapContext[K comparable, V any]( ctx context.Context, - maxTTL time.Duration, + defaultTTL time.Duration, length int, pruneInterval time.Duration, - refreshLastAccessOnGet bool, + refreshOnLoad bool, ) (m *Map[K, V]) { if length < 0 { length = 0 } m = &Map[K, V]{ - m: make(map[K]*mapItem[V], length), - refresh: refreshLastAccessOnGet, - stop: make(chan bool), + m: make(map[K]*mapItem[V], length), + defaultTTL: defaultTTL, + refreshOnLoad: refreshOnLoad, + stop: make(chan bool), } go func() { @@ -76,7 +97,7 @@ func NewMap[K comparable, V any]( currentTime := now.UnixNano() m.mtx.Lock() maps.DeleteFunc(m.m, func(key K, item *mapItem[V]) bool { - return currentTime-item.lastAccess.Load() >= int64(maxTTL) + return currentTime-item.lastAccess.Load() >= int64(item.itemTTL) }) m.mtx.Unlock() } @@ -104,32 +125,56 @@ func (m *Map[K, V]) Length() int { return len(m.m) } -// Store will insert a value into the [Map]. Store is safe for concurrent use. +// Load will retrieve a value from the [Map], as well as a bool indicating whether the key was +// found. If the item was not found the value returned is undefined. Load is safe for concurrent +// use. +func (m *Map[K, V]) Load(key K) (value V, ok bool) { + return m.loadImpl(key, true) +} + +// LoadPassive will retrieve a value from the [Map] (without updating that value's time to live), +// as well as a bool indicating whether the key was found. If the item was not found the value +// returned is undefined. LoadPassive is safe for concurrent use. +func (m *Map[K, V]) LoadPassive(key K) (value V, ok bool) { + return m.loadImpl(key, false) +} + +// Store will insert a value into the [Map] with the default tome to live. If the key/value pair +// already exists, the last access time will be updated, but the TTL will not be changed. This +// is important if the key/value pair was created with a non-default TTL using [Map.StoreWithTTL]. +// Store is safe for concurrent use. func (m *Map[K, V]) Store(key K, value V) { m.mtx.Lock() defer m.mtx.Unlock() it, ok := m.m[key] if !ok { - it = &mapItem[V]{Value: value} + it = &mapItem[V]{ + itemTTL: m.defaultTTL, + } m.m[key] = it } + it.value = value it.touch() } -// Load will retrieve a value from the [Map], as well as a bool indicating whether the key was -// found. If the item was not found the value returned is undefined. Load is safe for concurrent -// use. -func (m *Map[K, V]) Load(key K) (value V, ok bool) { - return m.loadImpl(key, true) -} +// StoreWithTTL will insert a value into the [Map] with a custom time to live. If the key/value pair +// already exists, the last access time will be updated and the TTL will not be changed to the +// parameter value. Store is safe for concurrent use. +func (m *Map[K, V]) StoreWithTTL(key K, value V, TTL time.Duration) { + m.mtx.Lock() + defer m.mtx.Unlock() -// LoadPassive will retrieve a value from the [Map] (without updating that value's time to live), -// as well as a bool indicating whether the key was found. If the item was not found the value -// returned is undefined. LoadPassive is safe for concurrent use. -func (m *Map[K, V]) LoadPassive(key K) (value V, ok bool) { - return m.loadImpl(key, false) + it, ok := m.m[key] + if !ok { + it = &mapItem[V]{} + m.m[key] = it + } + + it.value = value + it.itemTTL = TTL + it.touch() } func (m *Map[K, V]) loadImpl(key K, update bool) (value V, ok bool) { @@ -142,9 +187,9 @@ func (m *Map[K, V]) loadImpl(key K, update bool) (value V, ok bool) { return } - value = it.Value + value = it.value - if !update || !m.refresh { + if !update || !m.refreshOnLoad { return } @@ -168,7 +213,7 @@ func (m *Map[K, V]) DeleteFunc(del func(key K, value V) bool) { defer m.mtx.Unlock() for key, item := range m.m { - if del(key, item.Value) { + if del(key, item.value) { delete(m.m, key) } } @@ -200,7 +245,7 @@ func (m *Map[K, V]) Range(f func(key K, value V) bool) { defer m.mtx.Unlock() for key, item := range m.m { - if !f(key, item.Value) { + if !f(key, item.value) { break } } diff --git a/map_test.go b/map_test.go index c3dcaf7..5e4f46a 100644 --- a/map_test.go +++ b/map_test.go @@ -16,7 +16,6 @@ import ( type MapTestSuite struct { suite.Suite - ctx context.Context maxTTL time.Duration pruneInterval time.Duration startSize int @@ -25,7 +24,6 @@ type MapTestSuite struct { } func (s *MapTestSuite) SetupSuite() { - s.ctx = context.Background() s.maxTTL = 300 * time.Millisecond s.pruneInterval = 100 * time.Millisecond s.startSize = 3 @@ -45,8 +43,8 @@ func TestMapTestSuite(t *testing.T) { } func (s *MapTestSuite) TestAllItemsExpired() { - refreshOnStore := true - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store("myString", "a b c") @@ -58,8 +56,8 @@ func (s *MapTestSuite) TestAllItemsExpired() { } func (s *MapTestSuite) TestClose() { - refreshOnStore := true - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) tm.Store("myString", "a b c") tm.Store("int slice", []int{1, 2, 3}) @@ -72,14 +70,14 @@ func (s *MapTestSuite) TestClose() { } func (s *MapTestSuite) TestCancelContext() { - refreshOnStore := true - cancellableCtx, cancelFunc := context.WithCancel(s.ctx) - tm := ttl.NewMap[string, any]( + refreshOnLoad := true + cancellableCtx, cancelFunc := context.WithCancel(context.Background()) + tm := ttl.NewMapContext[string, any]( cancellableCtx, s.maxTTL, s.startSize, s.pruneInterval, - refreshOnStore) + refreshOnLoad) tm.Store("myString", "a b c") tm.Store("int slice", []int{1, 2, 3}) @@ -92,14 +90,14 @@ func (s *MapTestSuite) TestCancelContext() { } func (s *MapTestSuite) TestCloseAndCancelContextOrder() { - refreshOnStore := true - cancellableCtx, cancelFunc := context.WithCancel(s.ctx) - tm := ttl.NewMap[string, any]( + refreshOnLoad := true + cancellableCtx, cancelFunc := context.WithCancel(context.Background()) + tm := ttl.NewMapContext[string, any]( cancellableCtx, s.maxTTL, s.startSize, s.pruneInterval, - refreshOnStore) + refreshOnLoad) tm.Store("myString", "a b c") tm.Store("int slice", []int{1, 2, 3}) @@ -113,14 +111,14 @@ func (s *MapTestSuite) TestCloseAndCancelContextOrder() { } func (s *MapTestSuite) TestCancelContextAndCloseOrder() { - refreshOnStore := true - cancellableCtx, cancelFunc := context.WithCancel(s.ctx) - tm := ttl.NewMap[string, any]( + refreshOnLoad := true + cancellableCtx, cancelFunc := context.WithCancel(context.Background()) + tm := ttl.NewMapContext[string, any]( cancellableCtx, s.maxTTL, s.startSize, s.pruneInterval, - refreshOnStore) + refreshOnLoad) tm.Store("myString", "a b c") tm.Store("int slice", []int{1, 2, 3}) @@ -134,14 +132,14 @@ func (s *MapTestSuite) TestCancelContextAndCloseOrder() { } func (s *MapTestSuite) TestCancelContextAndCloseMany() { - refreshOnStore := true - cancellableCtx, cancelFunc := context.WithCancel(s.ctx) - tm := ttl.NewMap[string, any]( + refreshOnLoad := true + cancellableCtx, cancelFunc := context.WithCancel(context.Background()) + tm := ttl.NewMapContext[string, any]( cancellableCtx, s.maxTTL, s.startSize, s.pruneInterval, - refreshOnStore) + refreshOnLoad) for i := 0; i < 10; i++ { cancelFunc() @@ -152,8 +150,8 @@ func (s *MapTestSuite) TestCancelContextAndCloseMany() { } func (s *MapTestSuite) TestNoItemsExpired() { - refreshOnStore := true - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store("myString", "a b c") @@ -164,8 +162,8 @@ func (s *MapTestSuite) TestNoItemsExpired() { } func (s *MapTestSuite) TestTTLRefresh() { - refreshOnStore := true - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() refreshedKey := "int" @@ -199,8 +197,8 @@ func (s *MapTestSuite) TestTTLRefresh() { } func (s *MapTestSuite) TestWithNoRefresh() { - refreshOnStore := false - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := false + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store("myString", "a b c") @@ -223,9 +221,127 @@ func (s *MapTestSuite) TestWithNoRefresh() { s.Equal(0, tm.Length()) } +func (s *MapTestSuite) TestStoreExistingWritesNewValue() { + refreshOnLoad := true + tm := ttl.NewMap[int, int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) + defer tm.Close() + + tm.Store(0, 0) + tm.Store(0, 1) + tm.Store(0, 2) + + tm.Store(1, 1) + tm.Store(1, 2) + + s.Equal(2, tm.Length()) + v, ok := tm.Load(0) + if s.True(ok) { + s.Equal(2, v) + } + v, ok = tm.Load(1) + if s.True(ok) { + s.Equal(2, v) + } +} + +func (s *MapTestSuite) TestStoreWithTTLExistingWritesNewValue() { + refreshOnLoad := true + tm := ttl.NewMap[int, int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) + defer tm.Close() + + tm.StoreWithTTL(0, 0, time.Minute) + tm.StoreWithTTL(0, 1, time.Minute) + tm.StoreWithTTL(0, 2, time.Minute) + + tm.StoreWithTTL(1, 1, time.Minute) + tm.StoreWithTTL(1, 2, time.Minute) + + s.Equal(2, tm.Length()) + v, ok := tm.Load(0) + if s.True(ok) { + s.Equal(2, v) + } + v, ok = tm.Load(1) + if s.True(ok) { + s.Equal(2, v) + } +} + +func (s *MapTestSuite) TestStoreAndStoreWithTTLExistingWritesNewValue() { + refreshOnLoad := true + tm := ttl.NewMap[int, int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) + defer tm.Close() + + tm.Store(0, 0) + tm.Store(0, 1) + tm.StoreWithTTL(0, 2, time.Minute) + + tm.StoreWithTTL(1, 1, time.Minute) + tm.Store(1, 2) + + s.Equal(2, tm.Length()) + v, ok := tm.Load(0) + if s.True(ok) { + s.Equal(2, v) + } + v, ok = tm.Load(1) + if s.True(ok) { + s.Equal(2, v) + } +} + +func (s *MapTestSuite) TestStoreWithTTL() { + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) + defer tm.Close() + + tm.Store("myString", "a b c") + tm.StoreWithTTL("int slice", []int{1, 2, 3}, 2*s.maxTTL) + + time.Sleep(s.sleepTime) + + s.Equal(1, tm.Length()) + _, ok := tm.LoadPassive("int slice") + s.True(ok) + + time.Sleep(s.sleepTime) + + s.Equal(0, tm.Length()) +} + +func (s *MapTestSuite) TestStoreDoesNotResetCustomTTL() { + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) + defer tm.Close() + + tm.Store("myString", "a b c") + tm.StoreWithTTL("int slice", []int{1, 2, 3}, 2*s.maxTTL) + updatedSlice := []int{3, 2, 1} + tm.Store("int slice", updatedSlice) + + time.Sleep(s.sleepTime) + + s.Equal(1, tm.Length()) + v, ok := tm.LoadPassive("int slice") + if s.True(ok) { + value, ok := v.([]int) + if s.True(ok) { + s.Truef( + slices.Equal(updatedSlice, value), + "Actual %v does not equal expected %v", + value, + updatedSlice) + } + } + + time.Sleep(s.sleepTime) + + s.Equal(0, tm.Length()) +} + func (s *MapTestSuite) TestDelete() { - refreshOnStore := true - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store("myString", "a b c") @@ -247,8 +363,8 @@ func (s *MapTestSuite) TestDelete() { } func (s *MapTestSuite) TestDeleteFuncByKey() { - refreshOnStore := true - tm := ttl.NewMap[int, string](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[int, string](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store(0, "zero") @@ -277,8 +393,8 @@ func (s *MapTestSuite) TestDeleteFuncByKey() { } func (s *MapTestSuite) TestDeleteFuncByValue() { - refreshOnStore := true - tm := ttl.NewMap[string, int](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store("zero", 0) @@ -309,8 +425,8 @@ func (s *MapTestSuite) TestDeleteFuncByValue() { } func (s *MapTestSuite) TestClear() { - refreshOnStore := true - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store("myString", "a b c") @@ -324,8 +440,8 @@ func (s *MapTestSuite) TestClear() { } func (s *MapTestSuite) TestLoadPassive() { - refreshOnStore := true - tm := ttl.NewMap[string, any](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[string, any](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store("myString", "a b c") @@ -352,8 +468,8 @@ func (s *MapTestSuite) TestLoadPassive() { } func (s *MapTestSuite) TestUInt64Key() { - refreshOnStore := true - tm := ttl.NewMap[uint64, string](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[uint64, string](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store(18446744073709551615, "largest") @@ -369,8 +485,8 @@ func (s *MapTestSuite) TestUInt64Key() { } func (s *MapTestSuite) TestUFloat32Key() { - refreshOnStore := true - tm := ttl.NewMap[float32, string](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[float32, string](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store(34000000000.12345, "largest") @@ -386,8 +502,8 @@ func (s *MapTestSuite) TestUFloat32Key() { } func (s *MapTestSuite) TestByteKey() { - refreshOnStore := true - tm := ttl.NewMap[byte, string](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[byte, string](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() tm.Store(0x41, "A") @@ -407,8 +523,8 @@ func (s *MapTestSuite) TestStructKey() { y int } - refreshOnStore := true - tm := ttl.NewMap[pos, string](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[pos, string](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() topLeft := pos{0, 0} @@ -426,8 +542,8 @@ func (s *MapTestSuite) TestStructKey() { } func (s *MapTestSuite) TestRange() { - refreshOnStore := true - tm := ttl.NewMap[int, []int](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[int, []int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() firstSlice := []int{1, 2, 3} @@ -451,8 +567,8 @@ func (s *MapTestSuite) TestRange() { } func (s *MapTestSuite) TestRangeDelete() { - refreshOnStore := true - tm := ttl.NewMap[int, []int](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[int, []int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() firstSlice := []int{1, 2, 3} @@ -486,8 +602,8 @@ func (s *MapTestSuite) TestRangeDelete() { } func (s *MapTestSuite) TestRangeTransform() { - refreshOnStore := true - tm := ttl.NewMap[int, []int](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[int, []int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() firstSlice := []int{1, 2, 3} @@ -523,8 +639,8 @@ func (s *MapTestSuite) TestRangeTransform() { } func (s *MapTestSuite) TestConcurrentStoreExpire() { - refreshOnStore := true - tm := ttl.NewMap[uint64, uint64](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[uint64, uint64](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) defer tm.Close() startTime := time.Now() @@ -548,8 +664,8 @@ func (s *MapTestSuite) TestConcurrentStoreExpire() { } func (s *MapTestSuite) TestConcurrentStoreDelete() { - refreshOnStore := true - tm := ttl.NewMap[int, int](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[int, int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) tm.Close() // disable pruning startTime := time.Now() @@ -576,8 +692,8 @@ func (s *MapTestSuite) TestConcurrentStoreDelete() { } func (s *MapTestSuite) TestConcurrentStoreDeleteFuncOne() { - refreshOnStore := true - tm := ttl.NewMap[int, int](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[int, int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) tm.Close() // disable pruning startTime := time.Now() @@ -606,8 +722,8 @@ func (s *MapTestSuite) TestConcurrentStoreDeleteFuncOne() { } func (s *MapTestSuite) TestConcurrentStoreDeleteFuncMatch() { - refreshOnStore := true - tm := ttl.NewMap[int, int](s.ctx, s.maxTTL, s.startSize, s.pruneInterval, refreshOnStore) + refreshOnLoad := true + tm := ttl.NewMap[int, int](s.maxTTL, s.startSize, s.pruneInterval, refreshOnLoad) tm.Close() // disable pruning startTime := time.Now()