Skip to content

Commit 6b9c0e9

Browse files
committed
CBG-5507 protect LeakyBucket callbacks with mutex
1 parent 75b8bd0 commit 6b9c0e9

3 files changed

Lines changed: 300 additions & 80 deletions

File tree

base/constants_syncdocs_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ func TestInitSyncInfoErrors(t *testing.T) {
444444
assert.NoError(t, err)
445445
}()
446446
shouldFailAdd.Store(false)
447-
ds.config.WriteCasCallback = test.writeCasCallback
447+
ds.SetWriteCasCallback(test.writeCasCallback)
448448
requiresResync, requiresAttachmentMigration, err := InitSyncInfo(ctx, ds, expectedMetadataID, nil)
449449
if test.expectedError != "" {
450450
require.Error(t, err)

base/leaky_bucket.go

Lines changed: 232 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"expvar"
1515
"fmt"
1616
"slices"
17+
"sync"
1718

1819
sgbucket "github.com/couchbase/sg-bucket"
1920
)
@@ -23,17 +24,19 @@ var _ Bucket = &LeakyBucket{}
2324
// A wrapper around a Bucket to support forced errors. For testing use only.
2425
type LeakyBucket struct {
2526
bucket Bucket
26-
config *LeakyBucketConfig
27+
_config *LeakyBucketConfig
28+
configLock sync.RWMutex
2729
collections map[string]*LeakyDataStore
2830
}
2931

3032
var _ sgbucket.BucketStore = &LeakyBucket{}
3133
var _ sgbucket.DynamicDataStoreBucket = &LeakyBucket{}
3234

35+
// NewLeakyBucket creates a wrapper around a Bucket to support forced errors. The configuration will be shared by all DataStores that belong to this bucket.
3336
func NewLeakyBucket(bucket Bucket, config LeakyBucketConfig) *LeakyBucket {
3437
return &LeakyBucket{
3538
bucket: bucket,
36-
config: &config,
39+
_config: &config,
3740
collections: make(map[string]*LeakyDataStore),
3841
}
3942
}
@@ -47,16 +50,11 @@ func (b *LeakyBucket) UUID(ctx context.Context) (string, error) {
4750
}
4851

4952
func (b *LeakyBucket) Close(ctx context.Context) {
50-
if !b.config.IgnoreClose {
53+
if !b.getIgnoreClose() {
5154
b.bucket.Close(ctx)
5255
}
5356
}
5457

55-
// For walrus handling, ignore close needs to be set after the bucket is initialized
56-
func (b *LeakyBucket) SetIgnoreClose(value bool) {
57-
b.config.IgnoreClose = value
58-
}
59-
6058
func (b *LeakyBucket) CloseAndDelete(ctx context.Context) error {
6159
if bucket, ok := b.bucket.(sgbucket.DeleteableStore); ok {
6260
return bucket.CloseAndDelete(ctx)
@@ -73,7 +71,7 @@ func (b *LeakyBucket) GetMaxVbno(ctx context.Context) (uint16, error) {
7371
}
7472

7573
func (b *LeakyBucket) DefaultDataStore(ctx context.Context) sgbucket.DataStore {
76-
return NewLeakyDataStore(b, b.bucket.DefaultDataStore(ctx), b.config)
74+
return NewLeakyDataStore(b, b.bucket.DefaultDataStore(ctx))
7775
}
7876

7977
func (b *LeakyBucket) ListDataStores(ctx context.Context) ([]sgbucket.DataStoreName, error) {
@@ -85,7 +83,7 @@ func (b *LeakyBucket) NamedDataStore(ctx context.Context, name sgbucket.DataStor
8583
if err != nil {
8684
return nil, err
8785
}
88-
return NewLeakyDataStore(b, dataStore, b.config), nil
86+
return NewLeakyDataStore(b, dataStore), nil
8987
}
9088

9189
func (b *LeakyBucket) GetUnderlyingBucket() Bucket {
@@ -172,9 +170,10 @@ type LeakyBucketConfig struct {
172170
}
173171

174172
func (b *LeakyBucket) StartDCPFeed(ctx context.Context, args sgbucket.FeedArguments, callback sgbucket.FeedEventCallbackFunc, dbStats *expvar.Map) error {
175-
if len(b.config.DCPFeedMissingDocs) > 0 {
173+
missingDocs := b.getDCPFeedMissingDocs()
174+
if len(missingDocs) > 0 {
176175
wrappedCallback := func(event sgbucket.FeedEvent) bool {
177-
if slices.Contains(b.config.DCPFeedMissingDocs, string(event.Key)) {
176+
if slices.Contains(missingDocs, string(event.Key)) {
178177
return false
179178
}
180179
return callback(event)
@@ -183,3 +182,224 @@ func (b *LeakyBucket) StartDCPFeed(ctx context.Context, args sgbucket.FeedArgume
183182
}
184183
return b.bucket.StartDCPFeed(ctx, args, callback, dbStats)
185184
}
185+
186+
func (b *LeakyBucket) getIgnoreClose() bool {
187+
b.configLock.RLock()
188+
defer b.configLock.RUnlock()
189+
return b._config.IgnoreClose
190+
}
191+
192+
func (b *LeakyBucket) getDCPFeedMissingDocs() []string {
193+
b.configLock.RLock()
194+
defer b.configLock.RUnlock()
195+
return b._config.DCPFeedMissingDocs
196+
}
197+
198+
func (b *LeakyBucket) getRawCallback() func(string) error {
199+
b.configLock.RLock()
200+
defer b.configLock.RUnlock()
201+
return b._config.GetRawCallback
202+
}
203+
204+
func (b *LeakyBucket) setRawCallback(fn func(string) error) {
205+
b.configLock.Lock()
206+
defer b.configLock.Unlock()
207+
b._config.GetRawCallback = fn
208+
}
209+
210+
func (b *LeakyBucket) getWithXattrCallback() func(string) error {
211+
b.configLock.RLock()
212+
defer b.configLock.RUnlock()
213+
return b._config.GetWithXattrCallback
214+
}
215+
216+
func (b *LeakyBucket) setWithXattrCallback(fn func(string) error) {
217+
b.configLock.Lock()
218+
defer b.configLock.Unlock()
219+
b._config.GetWithXattrCallback = fn
220+
}
221+
222+
func (b *LeakyBucket) getTouchCallback() func(string) error {
223+
b.configLock.RLock()
224+
defer b.configLock.RUnlock()
225+
return b._config.TouchCallback
226+
}
227+
228+
func (b *LeakyBucket) getAddCallback() func(string) (bool, error) {
229+
b.configLock.RLock()
230+
defer b.configLock.RUnlock()
231+
return b._config.AddCallback
232+
}
233+
234+
func (b *LeakyBucket) getForceErrorSetRawKeys() []string {
235+
b.configLock.RLock()
236+
defer b.configLock.RUnlock()
237+
return b._config.ForceErrorSetRawKeys
238+
}
239+
240+
func (b *LeakyBucket) getWriteCasCallback() func(string) (uint64, error) {
241+
b.configLock.RLock()
242+
defer b.configLock.RUnlock()
243+
return b._config.WriteCasCallback
244+
}
245+
246+
func (b *LeakyBucket) setWriteCasCallback(fn func(string) (uint64, error)) {
247+
b.configLock.Lock()
248+
defer b.configLock.Unlock()
249+
b._config.WriteCasCallback = fn
250+
}
251+
252+
func (b *LeakyBucket) getUpdateCallback() func(string) {
253+
b.configLock.RLock()
254+
defer b.configLock.RUnlock()
255+
return b._config.UpdateCallback
256+
}
257+
258+
func (b *LeakyBucket) setUpdateCallback(fn func(string)) {
259+
b.configLock.Lock()
260+
defer b.configLock.Unlock()
261+
b._config.UpdateCallback = fn
262+
}
263+
264+
func (b *LeakyBucket) getPostUpdateCallback() func(string) {
265+
b.configLock.RLock()
266+
defer b.configLock.RUnlock()
267+
return b._config.PostUpdateCallback
268+
}
269+
270+
func (b *LeakyBucket) setPostUpdateCallback(fn func(string)) {
271+
b.configLock.Lock()
272+
defer b.configLock.Unlock()
273+
b._config.PostUpdateCallback = fn
274+
}
275+
276+
func (b *LeakyBucket) getForceTimeoutErrorOnUpdateKeys() []string {
277+
b.configLock.RLock()
278+
defer b.configLock.RUnlock()
279+
return b._config.ForceTimeoutErrorOnUpdateKeys
280+
}
281+
282+
func (b *LeakyBucket) getIncrTemporaryFailCount() uint16 {
283+
b.configLock.RLock()
284+
defer b.configLock.RUnlock()
285+
return b._config.IncrTemporaryFailCount
286+
}
287+
288+
func (b *LeakyBucket) getIncrCallback() func() {
289+
b.configLock.RLock()
290+
defer b.configLock.RUnlock()
291+
return b._config.IncrCallback
292+
}
293+
294+
// decrementDDocGetErrorCount atomically decrements DDocGetErrorCount if positive.
295+
// Returns the post-decrement remaining count and whether the error should be returned.
296+
func (b *LeakyBucket) decrementDDocGetErrorCount() (remaining int, shouldFail bool) {
297+
b.configLock.Lock()
298+
defer b.configLock.Unlock()
299+
if b._config.DDocGetErrorCount > 0 {
300+
b._config.DDocGetErrorCount--
301+
return b._config.DDocGetErrorCount, true
302+
}
303+
return 0, false
304+
}
305+
306+
func (b *LeakyBucket) setDDocGetErrorCount(i int) {
307+
b.configLock.Lock()
308+
defer b.configLock.Unlock()
309+
b._config.DDocGetErrorCount = i
310+
}
311+
312+
// decrementDDocDeleteErrorCount atomically decrements DDocDeleteErrorCount if positive.
313+
// Returns the post-decrement remaining count and whether the error should be returned.
314+
func (b *LeakyBucket) decrementDDocDeleteErrorCount() (remaining int, shouldFail bool) {
315+
b.configLock.Lock()
316+
defer b.configLock.Unlock()
317+
if b._config.DDocDeleteErrorCount > 0 {
318+
b._config.DDocDeleteErrorCount--
319+
return b._config.DDocDeleteErrorCount, true
320+
}
321+
return 0, false
322+
}
323+
324+
func (b *LeakyBucket) setDDocDeleteErrorCount(i int) {
325+
b.configLock.Lock()
326+
defer b.configLock.Unlock()
327+
b._config.DDocDeleteErrorCount = i
328+
}
329+
330+
func (b *LeakyBucket) getQueryCallback() func(string, string, map[string]any) error {
331+
b.configLock.RLock()
332+
defer b.configLock.RUnlock()
333+
return b._config.QueryCallback
334+
}
335+
336+
func (b *LeakyBucket) setQueryCallback(fn func(string, string, map[string]any) error) {
337+
b.configLock.Lock()
338+
defer b.configLock.Unlock()
339+
b._config.QueryCallback = fn
340+
}
341+
342+
func (b *LeakyBucket) getPostQueryCallback() func(string, string, map[string]any) {
343+
b.configLock.RLock()
344+
defer b.configLock.RUnlock()
345+
return b._config.PostQueryCallback
346+
}
347+
348+
func (b *LeakyBucket) setPostQueryCallback(fn func(string, string, map[string]any)) {
349+
b.configLock.Lock()
350+
defer b.configLock.Unlock()
351+
b._config.PostQueryCallback = fn
352+
}
353+
354+
// getAndClearFirstTimeViewCustomPartialError atomically reads and clears the flag.
355+
func (b *LeakyBucket) getAndClearFirstTimeViewCustomPartialError() bool {
356+
b.configLock.Lock()
357+
defer b.configLock.Unlock()
358+
v := b._config.FirstTimeViewCustomPartialError
359+
if v {
360+
b._config.FirstTimeViewCustomPartialError = false
361+
}
362+
return v
363+
}
364+
365+
func (b *LeakyBucket) setFirstTimeViewCustomPartialError(v bool) {
366+
b.configLock.Lock()
367+
defer b.configLock.Unlock()
368+
b._config.FirstTimeViewCustomPartialError = v
369+
}
370+
371+
func (b *LeakyBucket) getWriteWithXattrCallback() func(string) {
372+
b.configLock.RLock()
373+
defer b.configLock.RUnlock()
374+
return b._config.WriteWithXattrCallback
375+
}
376+
377+
func (b *LeakyBucket) getSetXattrCallback() func(string) error {
378+
b.configLock.RLock()
379+
defer b.configLock.RUnlock()
380+
return b._config.SetXattrCallback
381+
}
382+
383+
func (b *LeakyBucket) getN1QLQueryCallback() func(context.Context, string, map[string]any, ConsistencyMode, bool) error {
384+
b.configLock.RLock()
385+
defer b.configLock.RUnlock()
386+
return b._config.N1QLQueryCallback
387+
}
388+
389+
func (b *LeakyBucket) getPostN1QLQueryCallback() func() {
390+
b.configLock.RLock()
391+
defer b.configLock.RUnlock()
392+
return b._config.PostN1QLQueryCallback
393+
}
394+
395+
func (b *LeakyBucket) setPostN1QLQueryCallback(fn func()) {
396+
b.configLock.Lock()
397+
defer b.configLock.Unlock()
398+
b._config.PostN1QLQueryCallback = fn
399+
}
400+
401+
func (b *LeakyBucket) getCreateIndexIfNotExistsCallback() func(string) {
402+
b.configLock.RLock()
403+
defer b.configLock.RUnlock()
404+
return b._config.CreateIndexIfNotExistsCallback
405+
}

0 commit comments

Comments
 (0)