Skip to content

Commit fa2606f

Browse files
authored
Merge pull request #113 from svanharmelen/fix/locking
bug: fix a logical (locking) error in the Loader.Load method
2 parents 5689e53 + afb7ee6 commit fa2606f

File tree

1 file changed

+19
-5
lines changed

1 file changed

+19
-5
lines changed

dataloader.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,31 @@ func (l *Loader[K, V]) Load(originalContext context.Context, key K) Thunk[V] {
209209
value *Result[V]
210210
}
211211

212-
// lock to prevent duplicate keys coming in before item has been added to cache.
212+
// We need to lock both the batchLock and cacheLock because the batcher can
213+
// reset the cache when either the batchCap or the wait time is reached.
214+
//
215+
// When we would only lock the cacheLock while doing l.cache.Get and/or
216+
// l.cache.Set, it could be that the batcher resets the cache after those
217+
// operations have finished but before the new request (if any) is send to the
218+
// batcher.
219+
//
220+
// In that case it is no longer guaranteed that the keys passed to the BatchFunc
221+
// function are unique as the cache has been reset so if the same key is
222+
// requested again before the new batcher is started, the same key will be
223+
// send to the batcher again causing unexpected behavior in the BatchFunc.
224+
l.batchLock.Lock()
213225
l.cacheLock.Lock()
226+
214227
if v, ok := l.cache.Get(ctx, key); ok {
228+
l.cacheLock.Unlock()
229+
l.batchLock.Unlock()
215230
defer finish(v)
216-
defer l.cacheLock.Unlock()
217231
return v
218232
}
219233

234+
defer l.batchLock.Unlock()
235+
defer l.cacheLock.Unlock()
236+
220237
thunk := func() (V, error) {
221238
result.mu.RLock()
222239
resultNotSet := result.value == nil
@@ -240,13 +257,11 @@ func (l *Loader[K, V]) Load(originalContext context.Context, key K) Thunk[V] {
240257
defer finish(thunk)
241258

242259
l.cache.Set(ctx, key, thunk)
243-
l.cacheLock.Unlock()
244260

245261
// this is sent to batch fn. It contains the key and the channel to return
246262
// the result on
247263
req := &batchRequest[K, V]{key, c}
248264

249-
l.batchLock.Lock()
250265
// start the batch window if it hasn't already started.
251266
if l.curBatcher == nil {
252267
l.curBatcher = l.newBatcher(l.silent, l.tracer)
@@ -274,7 +289,6 @@ func (l *Loader[K, V]) Load(originalContext context.Context, key K) Thunk[V] {
274289
l.reset()
275290
}
276291
}
277-
l.batchLock.Unlock()
278292

279293
return thunk
280294
}

0 commit comments

Comments
 (0)