summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2023-01-26 13:52:09 -0700
committerLuke Shumaker <lukeshu@lukeshu.com>2023-01-26 13:55:01 -0700
commit1e8afc0ceb1e200ded8921f0ef8411301ab9be6a (patch)
treee5ff8e787a01930ae6d6db469ba7837dc461d466
parent4d7ce9f864409dcfb84d2e027df0022076946583 (diff)
Add CacheMap
-rw-r--r--cachemap.go94
1 files changed, 94 insertions, 0 deletions
diff --git a/cachemap.go b/cachemap.go
new file mode 100644
index 0000000..90d2274
--- /dev/null
+++ b/cachemap.go
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package typedsync
+
+import (
+ "sync"
+)
+
+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 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 Map, a CacheMap has both more space overhead
+// and more time overhead.
+type CacheMap[K mapkey, V any] struct {
+ inner Map[K, *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) {
+ _actual, loaded := m.inner.LoadOrStore(key, &cacheVal[V]{v: 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 := &cacheVal[V]{}
+ _value.wg.Add(1)
+ _actual, loaded := m.inner.LoadOrStore(key, _value)
+ if loaded {
+ _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) {
+ m.inner.Store(key, &cacheVal[V]{v: value})
+}