Skip to content

Commit 42028f7

Browse files
authored
kv/etcd: Fix race condition within unit test for Watch method (#6479)
Copied from grafana/dskit#336 Fixes a race condition during setup for Client.Watch tests where a conditional variable broadcast was missed by the caller waiting on the conditional variable. Verified by running the tests many times with a timeout: ``` go test -timeout=10s -count=10000 -run=TestMockKV_Watch ./pkg/ring/kv/etcd/ ``` Fixes: - #6478 - #5564 Signed-off-by: Charlie Le <[email protected]>
1 parent a8ee1a2 commit 42028f7

File tree

1 file changed

+22
-22
lines changed

1 file changed

+22
-22
lines changed

pkg/ring/kv/etcd/mock_test.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -306,24 +306,28 @@ func TestMockKV_Watch(t *testing.T) {
306306
// returned wait group.
307307
setupWatchTest := func(key string, prefix bool) (*mockKV, context.CancelFunc, chan *clientv3.Event, *sync.WaitGroup) {
308308
kv := newMockKV()
309-
// Use a condition to make sure the goroutine has started using the watch before
309+
// Use a WaitGroup to make sure the goroutine has started using the watch before
310310
// we do anything to the mockKV that would emit an event the watcher is expecting
311-
cond := sync.NewCond(&sync.Mutex{})
312-
wg := sync.WaitGroup{}
311+
started := sync.WaitGroup{}
312+
// Use another WaitGroup so that callers can tell when the channel returned by watch
313+
// method is closed and the watch is complete.
314+
complete := sync.WaitGroup{}
315+
313316
ch := make(chan *clientv3.Event)
314317
ctx, cancel := context.WithCancel(context.Background())
315318

316-
wg.Add(1)
319+
started.Add(1)
320+
complete.Add(1)
317321
go func() {
318-
defer wg.Done()
322+
defer complete.Done()
319323

320324
var ops []clientv3.OpOption
321325
if prefix {
322326
ops = []clientv3.OpOption{clientv3.WithPrefix()}
323327
}
324328

325329
watch := kv.Watch(ctx, key, ops...)
326-
cond.Broadcast()
330+
started.Done()
327331

328332
for e := range watch {
329333
if len(e.Events) > 0 {
@@ -332,33 +336,29 @@ func TestMockKV_Watch(t *testing.T) {
332336
}
333337
}()
334338

335-
// Wait for the watcher goroutine to start actually watching
336-
cond.L.Lock()
337-
cond.Wait()
338-
cond.L.Unlock()
339-
340-
return kv, cancel, ch, &wg
339+
started.Wait()
340+
return kv, cancel, ch, &complete
341341
}
342342

343-
t.Run("watch stopped by context", func(t *testing.T) {
343+
t.Run("watch stopped by context", func(*testing.T) {
344344
// Ensure we can use the cancel method of the context given to the watch
345345
// to stop the watch
346-
_, cancel, _, wg := setupWatchTest("/bar", false)
346+
_, cancel, _, complete := setupWatchTest("/bar", false)
347347
cancel()
348-
wg.Wait()
348+
complete.Wait()
349349
})
350350

351-
t.Run("watch stopped by close", func(t *testing.T) {
351+
t.Run("watch stopped by close", func(*testing.T) {
352352
// Ensure we can use the Close method of the mockKV given to the watch
353353
// to stop the watch
354-
kv, _, _, wg := setupWatchTest("/bar", false)
354+
kv, _, _, complete := setupWatchTest("/bar", false)
355355
_ = kv.Close()
356-
wg.Wait()
356+
complete.Wait()
357357
})
358358

359359
t.Run("watch exact key", func(t *testing.T) {
360360
// watch for events with key "/bar" and send them via the channel
361-
kv, cancel, ch, wg := setupWatchTest("/bar", false)
361+
kv, cancel, ch, complete := setupWatchTest("/bar", false)
362362

363363
_, err := kv.Put(context.Background(), "/foo", "1")
364364
require.NoError(t, err)
@@ -371,12 +371,12 @@ func TestMockKV_Watch(t *testing.T) {
371371
assert.Equal(t, []byte("/bar"), event.Kv.Key)
372372

373373
cancel()
374-
wg.Wait()
374+
complete.Wait()
375375
})
376376

377377
t.Run("watch prefix match", func(t *testing.T) {
378378
// watch for events with the prefix "/b" and send them via the channel
379-
kv, cancel, ch, wg := setupWatchTest("/b", true)
379+
kv, cancel, ch, complete := setupWatchTest("/b", true)
380380

381381
_, err := kv.Delete(context.Background(), "/foo")
382382
require.NoError(t, err)
@@ -389,6 +389,6 @@ func TestMockKV_Watch(t *testing.T) {
389389
assert.Equal(t, []byte("/bar"), event.Kv.Key)
390390

391391
cancel()
392-
wg.Wait()
392+
complete.Wait()
393393
})
394394
}

0 commit comments

Comments
 (0)