summaryrefslogtreecommitdiff
path: root/syncutil/cachemap.go
diff options
context:
space:
mode:
Diffstat (limited to 'syncutil/cachemap.go')
-rw-r--r--syncutil/cachemap.go116
1 files changed, 116 insertions, 0 deletions
diff --git a/syncutil/cachemap.go b/syncutil/cachemap.go
new file mode 100644
index 0000000..8eab4bc
--- /dev/null
+++ b/syncutil/cachemap.go
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package syncutil
+
+import (
+ "sync"
+
+ "git.lukeshu.com/go/containers/typedsync"
+)
+
+type cacheVal[V any] struct {
+ wg sync.WaitGroup
+ v V
+}
+
+// The techniques used by CacheMap are similar to the techniques used
+// by encoding/json's type cache.
+
+// A CacheMap is similar to a typedsync.Map, but also has a
+// .LoadOrCompute sibling to .LoadOrStore. This is useful for caching
+// the results of computation where it is undesirable for concurrent
+// calls to duplicate work.
+//
+// Compared to a plain typedsync.Map, a CacheMap has both more space
+// overhead and more time overhead.
+type CacheMap[K mapkey, V any] struct {
+ inner typedsync.Map[K, *cacheVal[V]]
+ pool typedsync.Pool[*cacheVal[V]]
+}
+
+func (m *CacheMap[K, V]) Delete(key K) {
+ m.inner.Delete(key)
+}
+
+// Load returns the value stored in the map for a key. If the value
+// for that key is actively being computed by LoadOrCompute, this
+// blocks until the value has been computed.
+func (m *CacheMap[K, V]) Load(key K) (value V, ok bool) {
+ _value, ok := m.inner.Load(key)
+ if !ok {
+ var zero V
+ return zero, false
+ }
+ _value.wg.Wait()
+ return _value.v, true
+}
+
+// LoadAndDelete deletes the value for a key, returning the previous
+// value if any. The loaded result reports whether the key was
+// present. If the value for that key was actively being computed by
+// LoadOrCompute, this immediately removes the partial value from the
+// CacheMap, but does not return until the computation has finished.
+func (m *CacheMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
+ _value, ok := m.inner.LoadAndDelete(key)
+ if !ok {
+ var zero V
+ return zero, false
+ }
+ _value.wg.Wait()
+ return _value.v, true
+}
+
+// LoadOrStore returns the existing value for the key if
+// present. Otherwise, it stores and returns the given value. The
+// loaded result is true if the value was loaded, false if stored. If
+// the value for that key is actively being computed by LoadOrCompute,
+// this blocks until the value has been computed.
+func (m *CacheMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
+ _value, _ := m.pool.Get()
+ if _value == nil {
+ _value = new(cacheVal[V])
+ }
+ _value.v = value
+ _actual, loaded := m.inner.LoadOrStore(key, _value)
+ if loaded {
+ *_value = cacheVal[V]{}
+ m.pool.Put(_value)
+ _actual.wg.Wait()
+ }
+ return _actual.v, loaded
+}
+
+// LoadOrCompute returns the existing value for the key if present.
+// Otherwise, it computes and stores a value using the given function.
+// The loaded result is true if the value was loaded, false if
+// computed. If a prior call to LoadOrCompute is still computing the
+// value for that key, a latter call blocks until the computation is
+// complete, and then returns the initial call's value.
+func (m *CacheMap[K, V]) LoadOrCompute(key K, fn func(K) V) (actual V, loaded bool) {
+ _value, _ := m.pool.Get()
+ if _value == nil {
+ _value = new(cacheVal[V])
+ }
+ _value.wg.Add(1)
+ _actual, loaded := m.inner.LoadOrStore(key, _value)
+ if loaded {
+ *_value = cacheVal[V]{}
+ m.pool.Put(_value)
+ _actual.wg.Wait()
+ } else {
+ _actual.v = fn(key)
+ _actual.wg.Done()
+ }
+ return _actual.v, loaded
+}
+
+func (m *CacheMap[K, V]) Store(key K, value V) {
+ _value, _ := m.pool.Get()
+ if _value == nil {
+ _value = new(cacheVal[V])
+ }
+ _value.v = value
+ m.inner.Store(key, _value)
+}