@@ -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,32 @@ 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 ] {
187
- cacheResults := make ([]Result [T ], 0 , len (c .caches ))
188
- for _ , cache := range c .caches {
189
- cacheResults = append (cacheResults , cache .Get ())
174
+
175
+ func (c * listMerger [T , V ]) prepareResultsLocked () []Result [T ] {
176
+ cacheResults := make ([]Result [T ], len (c .caches ))
177
+ ch := make (chan struct {
178
+ int
179
+ Result [T ]
180
+ }, len (c .caches ))
181
+ for i := range c .caches {
182
+ go func (index int ) {
183
+ ch <- struct {
184
+ int
185
+ Result [T ]
186
+ }{
187
+ index ,
188
+ c .caches [index ].Get (),
189
+ }
190
+ }(i )
191
+ }
192
+ for i := 0 ; i < len (c .caches ); i ++ {
193
+ res := <- ch
194
+ cacheResults [res .int ] = res .Result
190
195
}
191
196
return cacheResults
192
197
}
193
198
194
- func (c * listMerger [T , V ]) needsRunning (results []Result [T ]) bool {
199
+ func (c * listMerger [T , V ]) needsRunningLocked (results []Result [T ]) bool {
195
200
if c .cacheResults == nil {
196
201
return true
197
202
}
@@ -211,8 +216,10 @@ func (c *listMerger[T, V]) needsRunning(results []Result[T]) bool {
211
216
}
212
217
213
218
func (c * listMerger [T , V ]) Get () Result [V ] {
214
- cacheResults := c .prepareResults ()
215
- if c .needsRunning (cacheResults ) {
219
+ c .lock .Lock ()
220
+ defer c .lock .Unlock ()
221
+ cacheResults := c .prepareResultsLocked ()
222
+ if c .needsRunningLocked (cacheResults ) {
216
223
c .cacheResults = cacheResults
217
224
c .result = c .mergeFn (c .cacheResults )
218
225
}
@@ -238,7 +245,7 @@ func NewTransformer[T, V any](transformerFn func(Result[T]) Result[V], source Da
238
245
239
246
// NewSource creates a new cache that generates some data. This
240
247
// will always be called since we don't know the origin of the data and
241
- // if it needs to be updated or not.
248
+ // if it needs to be updated or not. sourceFn MUST be thread-safe.
242
249
func NewSource [T any ](sourceFn func () Result [T ]) Data [T ] {
243
250
c := source [T ](sourceFn )
244
251
return & c
@@ -259,25 +266,24 @@ func NewStaticSource[T any](staticFn func() Result[T]) Data[T] {
259
266
}
260
267
261
268
type static [T any ] struct {
269
+ once sync.Once
262
270
fn func () Result [T ]
263
- result * Result [T ]
271
+ result Result [T ]
264
272
}
265
273
266
274
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
275
+ c .once .Do (func () {
276
+ c .result = c .fn ()
277
+ })
278
+ return c .result
272
279
}
273
280
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
281
+ // Replaceable is a cache that carries the result even when the cache is
282
+ // replaced. This is the type that should typically be stored in
277
283
// structs.
278
284
type Replaceable [T any ] struct {
279
285
cache atomic.Pointer [Data [T ]]
280
- result * Result [T ]
286
+ result atomic. Pointer [ Result [T ] ]
281
287
}
282
288
283
289
// Get retrieves the data from the underlying source. [Replaceable]
@@ -286,23 +292,21 @@ type Replaceable[T any] struct {
286
292
// previously had returned a success, that success will be returned
287
293
// instead. If the cache fails but we never returned a success, that
288
294
// 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
295
func (c * Replaceable [T ]) Get () Result [T ] {
297
296
result := (* c .cache .Load ()).Get ()
298
- if result .Err != nil && c .result != nil && c .result .Err == nil {
299
- return * c .result
297
+
298
+ for {
299
+ cResult := c .result .Load ()
300
+ if result .Err != nil && cResult != nil && cResult .Err == nil {
301
+ return * cResult
302
+ }
303
+ if c .result .CompareAndSwap (cResult , & result ) {
304
+ return result
305
+ }
300
306
}
301
- c .result = & result
302
- return * c .result
303
307
}
304
308
305
- // Replace changes the cache in a thread-safe way .
309
+ // Replace changes the cache.
306
310
func (c * Replaceable [T ]) Replace (cache Data [T ]) {
307
311
c .cache .Swap (& cache )
308
312
}
0 commit comments