diff --git a/discard.go b/discard.go index c6aa221f2..626113bcf 100644 --- a/discard.go +++ b/discard.go @@ -19,7 +19,7 @@ import ( // discardStats keeps track of the amount of data that could be discarded for // a given logfile. type discardStats struct { - sync.Mutex + sync.RWMutex *z.MmapFile opt Options @@ -135,6 +135,8 @@ func (lf *discardStats) Update(fidu uint32, discard int64) int64 { } func (lf *discardStats) Iterate(f func(fid, stats uint64)) { + lf.RLock() + defer lf.RUnlock() for slot := 0; slot < lf.nextEmptySlot; slot++ { idx := 16 * slot f(lf.get(idx), lf.get(idx+8)) @@ -143,8 +145,6 @@ func (lf *discardStats) Iterate(f func(fid, stats uint64)) { // MaxDiscard returns the file id with maximum discard bytes. func (lf *discardStats) MaxDiscard() (uint32, int64) { - lf.Lock() - defer lf.Unlock() var maxFid, maxVal uint64 lf.Iterate(func(fid, val uint64) { diff --git a/discard_test.go b/discard_test.go index 7f3924750..61e7ae400 100644 --- a/discard_test.go +++ b/discard_test.go @@ -7,6 +7,7 @@ package badger import ( "os" + "sync" "testing" "github.com/stretchr/testify/require" @@ -64,3 +65,40 @@ func TestReloadDiscardStats(t *testing.T) { require.Zero(t, ds2.Update(uint32(1), 0)) require.Equal(t, 1, int(ds2.Update(uint32(2), 0))) } + + +func TestDiscardStats_ConcurrentRace(t *testing.T) { + dir, err := os.MkdirTemp("", "badger-test") + require.NoError(t, err) + defer removeDir(dir) + + opt := DefaultOptions(dir) + db, err := Open(opt) + require.NoError(t, err) + defer db.Close() + ds := db.vlog.discardStats + + var wg sync.WaitGroup + for i := 0; i < 20; i++ { + wg.Add(1) + go func(id uint32) { + defer wg.Done() + for j := 0; j < 100; j++ { + ds.Update(id, int64(j*10)) + } + }(uint32(i)) + } + + for i := 0; i < 20; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 100; j++ { + ds.Iterate(func(id, val uint64) {}) + ds.MaxDiscard() + } + }() + } + wg.Wait() +} +