Skip to content

Commit 72e7166

Browse files
committed
minigc: add expiration
also add a copyright header
1 parent 33df4d0 commit 72e7166

File tree

2 files changed

+58
-16
lines changed

2 files changed

+58
-16
lines changed

minigc/cluster.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,41 @@ package minigc
33
import (
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.
2337
type 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

4056
func 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
5067
func (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
}

minigc/star_system.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
1-
// package minigc provides two types for light-weight galaxycache-style
1+
/*
2+
Copyright 2025 Vimeo Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package minigc provides two types for light-weight galaxycache-style
218
// self-hydrating in-process caches.
319
//
420
// - [StarSystem] is a lightweight cache that uses a single mutex that's held

0 commit comments

Comments
 (0)