forked from evergreen-ci/evergreen
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cached_value.go
167 lines (140 loc) · 4.73 KB
/
cached_value.go
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package util
import (
"errors"
"fmt"
"time"
)
// TODO write code generators to define additional forms of these types as needed.
// CachedIntValueRefresher provides a mechanism for CachedIntValues to
// update their values when the current cached value
// expires. Implementations are responsible for logging errors, as
// needed.
type CachedIntValueRefresher func(int) (int, bool)
// CachedIntValue represents a calculated int value saved in a
// database with a expiration time. When the data is not expired, the
// value is returned directly by the Get() method, otherwise, a
// refresh function is called, to update the value.
type CachedIntValue struct {
Value int `bson:"value"`
TTL time.Duration `bson:"ttl"`
CollectedAt time.Time `bson:"collected_at"`
refresher CachedIntValueRefresher
}
// NewCachedIntValue constructs a CachedIntValue object.
func NewCachedIntValue(start int, ttl time.Duration, refresh CachedIntValueRefresher) *CachedIntValue {
return &CachedIntValue{
Value: start,
TTL: ttl,
CollectedAt: time.Now(),
refresher: refresh,
}
}
// String implements fmt.Stringer reporting how stale the value is in
// the stale case.
func (v *CachedIntValue) String() string {
staleFor := time.Since(v.CollectedAt)
if staleFor > v.TTL {
return fmt.Sprintf("%d (stale; %s)", v.Value, staleFor)
}
return fmt.Sprintf("%d", v.Value)
}
// SetRefresher sets CachedIntValueRefresher for the object which is
// needed when reading CachedIntValue objects out of the database.
//
// It is not permissible to set the refresher to either nil or a value
// when it is *not* nil.
func (v *CachedIntValue) SetRefresher(r CachedIntValueRefresher) error {
if r == nil {
return errors.New("cannot set a nil refresher")
}
v.refresher = r
return nil
}
// Get returns the value, refreshing it when its stale. The "ok" value
// reports errors with the refresh process and alerts callers that
// the value might be stale.
func (v *CachedIntValue) Get() (int, bool) {
if time.Since(v.CollectedAt) < v.TTL {
return v.Value, true
}
if v.refresher == nil {
return v.Value, false
}
nv, ok := v.refresher(v.Value)
if !ok {
return v.Value, false
}
v.Value = nv
v.CollectedAt = time.Now()
return v.Value, true
}
type DurationStats struct {
Average time.Duration
StdDev time.Duration
}
// CachedDurationValueRefresher provides a mechanism for CachedDurationValues to
// update their values when the current cached value
// expires. Implementations are responsible for logging errors, as
// needed.
type CachedDurationValueRefresher func(DurationStats) (DurationStats, bool)
// CachedDurationValue represents a calculated int value saved in a
// database with a expiration time. When the data is not expired, the
// value is returned directly by the Get() method, otherwise, a
// refresh function is called, to update the value.
type CachedDurationValue struct {
Value time.Duration `bson:"value"`
StdDev time.Duration `bson:"std_dev"`
TTL time.Duration `bson:"ttl"`
CollectedAt time.Time `bson:"collected_at"`
refresher CachedDurationValueRefresher
}
// NewCachedDurationValue constructs a CachedDurationValue object.
func NewCachedDurationValue(start, ttl time.Duration, refresh CachedDurationValueRefresher) *CachedDurationValue {
return &CachedDurationValue{
Value: start,
TTL: ttl,
CollectedAt: time.Now(),
refresher: refresh,
}
}
// String implements fmt.Stringer reporting how stale the value is in
// the stale case.
func (v *CachedDurationValue) String() string {
staleFor := time.Since(v.CollectedAt)
if staleFor > v.TTL {
return fmt.Sprintf("%d (stale; %s)", v.Value, staleFor)
}
return v.Value.String()
}
// SetRefresher sets CachedDurationValueRefresher for the object which is
// needed when reading CachedDurationValue objects out of the database.
//
// It is not permissible to set the refresher to either nil or a value
// when it is *not* nil.
func (v *CachedDurationValue) SetRefresher(r CachedDurationValueRefresher) error {
if r == nil {
return errors.New("cannot set a nil refresher")
}
v.refresher = r
return nil
}
// Get returns the value, refreshing it when its stale. The "ok" value
// tells the caller that the value needs to be persisted and may have
// changed since the last time Get was called.
func (v *CachedDurationValue) Get() (DurationStats, bool) {
previous := DurationStats{Average: v.Value, StdDev: v.StdDev}
if time.Since(v.CollectedAt) < v.TTL {
return previous, false
}
if v.refresher == nil {
return previous, false
}
nv, ok := v.refresher(previous)
if !ok {
return previous, false
}
v.Value = nv.Average
v.StdDev = nv.StdDev
v.CollectedAt = time.Now()
return DurationStats{Average: v.Value, StdDev: v.StdDev}, true
}