java-topology/defects/drone-0002/patch/drone-0002.patch

82 lines
1.8 KiB
Diff

# UNDF: UNDF-2026-000001087
--- a/cache/ttl_cache.go
+++ b/cache/ttl_cache.go
@@ -14,6 +14,7 @@ import (
"context"
"fmt"
"slices"
+ "sync"
"sync/atomic"
"time"
@@ -25,6 +26,7 @@ type TTLCache[K comparable, V any] struct {
mx sync.RWMutex
cache map[K]cacheEntry[V]
purgeStop chan struct{}
+ group singleflightGroup[K, V]
getter Getter[K, V]
maxAge time.Duration
countHit atomic.Int64
@@ -200,13 +202,27 @@ func (c *TTLCache[K, V]) Get(ctx context.Context, key K) (V, error) {
item, ok := c.fetch(key, now)
if ok {
return item, nil
}
- item, err := c.getter.Find(ctx, key)
+ // Use singleflight to prevent thundering herd: concurrent goroutines that
+ // miss the cache for the same key share a single backend fetch instead of
+ // each issuing their own database call.
+ item, err, _ := c.group.Do(key, func() (V, error) {
+ return c.getter.Find(ctx, key)
+ })
if err != nil {
return nothing, fmt.Errorf("cache: failed to find one: %w", err)
}
c.mx.Lock()
c.cache[key] = cacheEntry[V]{
added: now,
data: item,
}
c.mx.Unlock()
return item, nil
}
+// singleflightGroup is a minimal generic singleflight implementation.
+type singleflightGroup[K comparable, V any] struct {
+ mu sync.Mutex
+ m map[K]*call[V]
+}
+
+type call[V any] struct {
+ wg sync.WaitGroup
+ val V
+ err error
+}
+
+func (g *singleflightGroup[K, V]) Do(key K, fn func() (V, error)) (V, error, bool) {
+ g.mu.Lock()
+ if g.m == nil {
+ g.m = make(map[K]*call[V])
+ }
+ if c, ok := g.m[key]; ok {
+ g.mu.Unlock()
+ c.wg.Wait()
+ return c.val, c.err, true
+ }
+ c := &call[V]{}
+ c.wg.Add(1)
+ g.m[key] = c
+ g.mu.Unlock()
+
+ c.val, c.err = fn()
+ c.wg.Done()
+
+ g.mu.Lock()
+ delete(g.m, key)
+ g.mu.Unlock()
+
+ return c.val, c.err, false
+}