feat(cooler): Cache.Pull returns and removes a value atomically

- Cache.Pull(ctx, key) ([]byte, bool, error), Laravel's Cache::pull
- Redis driver runs GETDEL (Redis 6.2+); memory driver uses otter Compute
  with InvalidateOp under the per-key lock; expired keys report not found
- TestPullSmoke over the memory driver and miniredis, ExampleCache_Pull
- README and docs/services/caching.md ("One-time values")
This commit is contained in:
Jakub Zych
2026-10-20 22:58:44 +02:00
parent 26ebb6c98c
commit 5e0a04e4da
7 changed files with 164 additions and 4 deletions

View File

@@ -84,6 +84,27 @@ func (m *memoryCache) Get(_ context.Context, key string) ([]byte, bool, error) {
return clone(e.val), true, nil
}
// Pull returns the value at key and removes it under otter's per-key lock, so
// of two concurrent callers at most one sees found=true. An expired entry is
// removed too and reported as not found.
func (m *memoryCache) Pull(_ context.Context, key string) ([]byte, bool, error) {
var (
out []byte
found bool
)
now := time.Now()
m.c.Compute(m.key(key), func(old memEntry, ok bool) (memEntry, otter.ComputeOp) {
if !ok {
return old, otter.CancelOp
}
if !old.expired(now) {
out, found = clone(old.val), true
}
return memEntry{}, otter.InvalidateOp
})
return out, found, nil
}
func (m *memoryCache) Set(_ context.Context, key string, value []byte, ttl time.Duration) error {
e := memEntry{val: clone(value)}
if ttl > 0 {