@@ -3,27 +3,41 @@ package minigc
33import (
44 "context"
55 "sync"
6+ "time"
67
78 "github.com/vimeo/galaxycache/lru"
89 "github.com/vimeo/galaxycache/singleflight"
910)
1011
11- type ClusterHydrateCB [K comparable , V any ] func (ctx context.Context , key K ) (V , error )
12+ // ClusterHydrateInfo provides context around the returned value
13+ type ClusterHydrateInfo struct {
14+ // Expiration is an absolute time after which this value is no longer
15+ // valid and should not be returned.
16+ // The zero-value means no-expiration.
17+ Expiration time.Time
18+ }
19+
20+ type ClusterHydrateCB [K comparable , V any ] func (ctx context.Context , key K ) (V , ClusterHydrateInfo , error )
21+
22+ type flightGroupVal [V any ] struct {
23+ val V
24+ hydInfo ClusterHydrateInfo
25+ }
1226
1327// Cluster is an in-process cache intended for in-memory data
1428// It's a thin wrapper around another cache implementation that takes care of
1529// filling cache misses instead of requiring Add() calls.
1630// It leverages the singleflight package to handle cases where hydration can
1731// fail and/or block.
1832//
19- // In contrast, StarSystem is designed for cases where constructing/hydrating/fetching a
33+ // In contrast, [ StarSystem] is designed for cases where constructing/hydrating/fetching a
2034// value is quick, can't fail, but not completely free. (It's a lighter-weight implementation)
2135//
2236// Note: the underlying cache implementation may change at any time.
2337type Cluster [K comparable , V any ] struct {
2438 lru lru.TypedCache [K , V ]
2539 hydrateCB ClusterHydrateCB [K , V ]
26- sfG singleflight.TypedGroup [K , V ]
40+ sfG singleflight.TypedGroup [K , flightGroupVal [ V ] ]
2741 mu sync.Mutex
2842}
2943
@@ -35,6 +49,8 @@ type ClusterParams[K comparable, V any] struct {
3549 // OnEvicted optionally specificies a callback function to be
3650 // executed when an typedEntry is purged from the cache.
3751 OnEvicted func (key K , value V )
52+
53+ MaxTTL time.Duration
3854}
3955
4056func NewCluster [K comparable , V any ](cb ClusterHydrateCB [K , V ], params ClusterParams [K , V ]) * Cluster [K , V ] {
@@ -47,34 +63,44 @@ func NewCluster[K comparable, V any](cb ClusterHydrateCB[K, V], params ClusterPa
4763 }
4864}
4965
66+ // Remove deletes the specified key from the cache
5067func (s * Cluster [K , V ]) Remove (key K ) {
5168 s .mu .Lock ()
5269 defer s .mu .Unlock ()
5370 s .lru .Remove (key )
5471}
5572
56- func (s * Cluster [K , V ]) get (key K ) (V , bool ) {
73+ func (s * Cluster [K , V ]) get (key K ) (V , ClusterHydrateInfo , bool ) {
5774 s .mu .Lock ()
5875 defer s .mu .Unlock ()
59- if v , ok := s .lru .Get (key ); ok {
60- return v , true
76+ if v , exp , ok := s .lru .GetWithExpiry (key ); ok {
77+ return v , ClusterHydrateInfo { Expiration : exp }, true
6178 }
6279 var vz V
63- return vz , false
80+ return vz , ClusterHydrateInfo {}, false
6481}
6582
66- func (s * Cluster [K , V ]) Get (ctx context.Context , key K ) (V , error ) {
67- return s .sfG .Do (key , func () (V , error ) {
68- if v , ok := s .get (key ); ok {
69- return v , nil
83+ // Get fetches the specified key from the cache, and calls the [ClusterHydrateCB] if not present.
84+ // Overlapping calls to Get for the same key will be singleflighted.
85+ func (s * Cluster [K , V ]) Get (ctx context.Context , key K ) (V , ClusterHydrateInfo , error ) {
86+ // Before involving singleflight, do a quick check for the key to
87+ // reduce the cost of the uncontended case.
88+ if v , hydInfo , ok := s .get (key ); ok {
89+ return v , hydInfo , nil
90+ }
91+
92+ fgv , doErr := s .sfG .Do (key , func () (flightGroupVal [V ], error ) {
93+ if v , hydInfo , ok := s .get (key ); ok {
94+ return flightGroupVal [V ]{val : v , hydInfo : hydInfo }, nil
7095 }
71- val , hydErr := s .hydrateCB (ctx , key )
96+ val , hydInfo , hydErr := s .hydrateCB (ctx , key )
7297 if hydErr != nil {
73- return val , hydErr
98+ return flightGroupVal [ V ]{} , hydErr
7499 }
75100 s .mu .Lock ()
76101 defer s .mu .Unlock ()
77- s .lru .Add (key , val )
78- return val , nil
102+ s .lru .AddExpiring (key , val , hydInfo . Expiration )
103+ return flightGroupVal [ V ]{ val : val , hydInfo : hydInfo } , nil
79104 })
105+ return fgv .val , fgv .hydInfo , doErr
80106}
0 commit comments