@@ -19,6 +19,8 @@ limitations under the License.
19
19
// operations are not repeated unnecessarily. The operations can be
20
20
// created as a tree, and replaced dynamically as needed.
21
21
//
22
+ // All the operations in this module are thread-safe.
23
+ //
22
24
// # Dependencies and types of caches
23
25
//
24
26
// This package uses a source/transform/sink model of caches to build
@@ -34,15 +36,6 @@ limitations under the License.
34
36
// replaced with a new one, and saves the previous results in case an
35
37
// error pops-up.
36
38
//
37
- // # Atomicity
38
- //
39
- // Most of the operations are not atomic/thread-safe, except for
40
- // [Replaceable.Replace] which can be performed while the objects are
41
- // being read. Specifically, `Get` methods are NOT thread-safe. Never
42
- // call `Get()` without a lock on a multi-threaded environment, since
43
- // it's usually performing updates to caches that will require write
44
- // operations.
45
- //
46
39
// # Etags
47
40
//
48
41
// Etags in this library is a cache version identifier. It doesn't
@@ -57,6 +50,7 @@ package cached
57
50
58
51
import (
59
52
"fmt"
53
+ "sync"
60
54
"sync/atomic"
61
55
)
62
56
@@ -100,13 +94,6 @@ func (r Result[T]) Get() Result[T] {
100
94
type Data [T any ] interface {
101
95
// Returns the cached data, as well as an "etag" to identify the
102
96
// version of the cache, or an error if something happened.
103
- //
104
- // # Important note
105
- //
106
- // This method is NEVER thread-safe, never assume it is OK to
107
- // call `Get()` without holding a proper mutex in a
108
- // multi-threaded environment, especially since `Get()` will
109
- // usually update the cache and perform write operations.
110
97
Get () Result [T ]
111
98
}
112
99
@@ -155,6 +142,7 @@ func NewMerger[K comparable, T, V any](mergeFn func(results map[K]Result[T]) Res
155
142
}
156
143
157
144
type listMerger [T , V any ] struct {
145
+ lock sync.Mutex
158
146
mergeFn func ([]Result [T ]) Result [V ]
159
147
caches []Data [T ]
160
148
cacheResults []Result [T ]
@@ -183,15 +171,15 @@ func NewListMerger[T, V any](mergeFn func(results []Result[T]) Result[V], caches
183
171
caches : caches ,
184
172
}
185
173
}
186
- func (c * listMerger [T , V ]) prepareResults () []Result [T ] {
174
+ func (c * listMerger [T , V ]) prepareResultsLocked () []Result [T ] {
187
175
cacheResults := make ([]Result [T ], 0 , len (c .caches ))
188
176
for _ , cache := range c .caches {
189
177
cacheResults = append (cacheResults , cache .Get ())
190
178
}
191
179
return cacheResults
192
180
}
193
181
194
- func (c * listMerger [T , V ]) needsRunning (results []Result [T ]) bool {
182
+ func (c * listMerger [T , V ]) needsRunningLocked (results []Result [T ]) bool {
195
183
if c .cacheResults == nil {
196
184
return true
197
185
}
@@ -211,8 +199,10 @@ func (c *listMerger[T, V]) needsRunning(results []Result[T]) bool {
211
199
}
212
200
213
201
func (c * listMerger [T , V ]) Get () Result [V ] {
214
- cacheResults := c .prepareResults ()
215
- if c .needsRunning (cacheResults ) {
202
+ c .lock .Lock ()
203
+ defer c .lock .Unlock ()
204
+ cacheResults := c .prepareResultsLocked ()
205
+ if c .needsRunningLocked (cacheResults ) {
216
206
c .cacheResults = cacheResults
217
207
c .result = c .mergeFn (c .cacheResults )
218
208
}
@@ -238,7 +228,7 @@ func NewTransformer[T, V any](transformerFn func(Result[T]) Result[V], source Da
238
228
239
229
// NewSource creates a new cache that generates some data. This
240
230
// will always be called since we don't know the origin of the data and
241
- // if it needs to be updated or not.
231
+ // if it needs to be updated or not. sourceFn MUST be thread-safe.
242
232
func NewSource [T any ](sourceFn func () Result [T ]) Data [T ] {
243
233
c := source [T ](sourceFn )
244
234
return & c
@@ -259,25 +249,24 @@ func NewStaticSource[T any](staticFn func() Result[T]) Data[T] {
259
249
}
260
250
261
251
type static [T any ] struct {
252
+ once sync.Once
262
253
fn func () Result [T ]
263
- result * Result [T ]
254
+ result Result [T ]
264
255
}
265
256
266
257
func (c * static [T ]) Get () Result [T ] {
267
- if c .result == nil {
268
- result := c .fn ()
269
- c .result = & result
270
- }
271
- return * c .result
258
+ c .once .Do (func () {
259
+ c .result = c .fn ()
260
+ })
261
+ return c .result
272
262
}
273
263
274
- // Replaceable is a cache that carries the result even when the
275
- // cache is replaced. The cache can be replaced atomically (without any
276
- // lock held). This is the type that should typically be stored in
264
+ // Replaceable is a cache that carries the result even when the cache is
265
+ // replaced. This is the type that should typically be stored in
277
266
// structs.
278
267
type Replaceable [T any ] struct {
279
268
cache atomic.Pointer [Data [T ]]
280
- result * Result [T ]
269
+ result atomic. Pointer [ Result [T ] ]
281
270
}
282
271
283
272
// Get retrieves the data from the underlying source. [Replaceable]
@@ -286,23 +275,21 @@ type Replaceable[T any] struct {
286
275
// previously had returned a success, that success will be returned
287
276
// instead. If the cache fails but we never returned a success, that
288
277
// failure is returned.
289
- //
290
- // # Important note
291
- //
292
- // As all implementations of Get, this implementation is NOT
293
- // thread-safe. Please properly lock a mutex before calling this method
294
- // if you are in a multi-threaded environment, since this method will
295
- // update the cache and perform write operations.
296
278
func (c * Replaceable [T ]) Get () Result [T ] {
297
279
result := (* c .cache .Load ()).Get ()
298
- if result .Err != nil && c .result != nil && c .result .Err == nil {
299
- return * c .result
280
+
281
+ for {
282
+ cResult := c .result .Load ()
283
+ if result .Err != nil && cResult != nil && cResult .Err == nil {
284
+ return * cResult
285
+ }
286
+ if c .result .CompareAndSwap (cResult , & result ) {
287
+ return result
288
+ }
300
289
}
301
- c .result = & result
302
- return * c .result
303
290
}
304
291
305
- // Replace changes the cache in a thread-safe way .
292
+ // Replace changes the cache.
306
293
func (c * Replaceable [T ]) Replace (cache Data [T ]) {
307
294
c .cache .Swap (& cache )
308
295
}
0 commit comments