-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathfeature_cache.go
More file actions
106 lines (82 loc) · 2.46 KB
/
feature_cache.go
File metadata and controls
106 lines (82 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package unleash
import (
"fmt"
"sync"
"sync/atomic"
"github.com/Unleash/unleash-go-sdk/v6/api"
)
type featureCache struct {
featureState atomic.Value
mu sync.Mutex
}
func newFeatureCache(baseState *api.FeatureResponse) *featureCache {
if baseState == nil {
baseState = &api.FeatureResponse{
Features: make([]api.Feature, 0),
Segments: make([]api.Segment, 0),
}
}
featureState := &FeatureMemoryState{
Features: baseState.FeatureMap(),
Segments: baseState.SegmentsMap(),
}
featureCache := &featureCache{
featureState: atomic.Value{},
}
featureCache.featureState.Store(featureState)
return featureCache
}
func (dp *featureCache) updateFromApiResponse(api *api.ApiResponse) (*api.FeatureResponse, error) {
if api.IsFullResponse() {
return dp.updateFromFullResponse(api.Full)
}
if api.IsDeltaResponse() {
return dp.updateFromDelta(api.Delta)
}
return nil, fmt.Errorf("unknown API response type")
}
func (dp *featureCache) updateFromFullResponse(full *api.FeatureResponse) (*api.FeatureResponse, error) {
newState := &FeatureMemoryState{
Features: full.FeatureMap(),
Segments: full.SegmentsMap(),
}
dp.featureState.Store(newState)
return full, nil
}
// It's hard to make this both concurrency safe and atomic for both readers and writes. To build our new state we need
// to hold a lock over the entire process to prevent internal races setting the final state out of order.
// However, once we have built the new state we can atomically swap it. This gives us mutex locked writes but
// lock free reads via snapshot()
func (dp *featureCache) updateFromDelta(delta *api.ClientFeaturesDelta) (*api.FeatureResponse, error) {
if delta == nil {
return nil, fmt.Errorf("delta is nil")
}
dp.mu.Lock()
defer dp.mu.Unlock()
snap := dp.snapshot()
featMap := make(map[string]*api.Feature, len(snap.Features))
for name, f := range snap.Features {
featMap[name] = f
}
segMap := make(map[int][]api.Constraint, len(snap.Segments))
for id, constraints := range snap.Segments {
segMap[id] = constraints
}
deltaState := &api.DeltaState{
Features: featMap,
Segments: segMap,
}
for _, event := range delta.Events {
event.Apply(deltaState)
}
newState := &FeatureMemoryState{
Features: deltaState.Features,
Segments: deltaState.Segments,
}
dp.featureState.Store(newState)
return newState.asApiResponse(), nil
}
func (dp *featureCache) snapshot() *FeatureMemoryState {
v := dp.featureState.Load()
return v.(*FeatureMemoryState)
}