swarm: Fix localstore test deadlock with race detector (#19153)

* swarm/storage/localstore: close localstore in two tests

* swarm/storage/localstore: fix a possible deadlock in tests

* swarm/storage/localstore: re-enable pull subs tests for travis race

* swarm/storage/localstore: stop sending to errChan on context done in tests

* swarm/storage/localstore: better want check in readPullSubscriptionBin

* swarm/storage/localstore: protect chunk put with addr lock in tests

* swamr/storage/localstore: wait for gc and writeGCSize workers on Close

* swarm/storage/localstore: more correct testDB_collectGarbageWorker

* swarm/storage/localstore: set DB Close timeout to 5s
This commit is contained in:
Janoš Guljaš 2019-02-22 23:19:09 +01:00 committed by Viktor Trón
parent d9adcd3a27
commit 02c28046a0
6 changed files with 73 additions and 66 deletions

View File

@ -117,6 +117,8 @@ var (
// run. GC run iterates on gcIndex and removes older items // run. GC run iterates on gcIndex and removes older items
// form retrieval and other indexes. // form retrieval and other indexes.
func (db *DB) collectGarbageWorker() { func (db *DB) collectGarbageWorker() {
defer close(db.collectGarbageWorkerDone)
for { for {
select { select {
case <-db.collectGarbageTrigger: case <-db.collectGarbageTrigger:
@ -132,7 +134,7 @@ func (db *DB) collectGarbageWorker() {
db.triggerGarbageCollection() db.triggerGarbageCollection()
} }
if testHookCollectGarbage != nil { if collectedCount > 0 && testHookCollectGarbage != nil {
testHookCollectGarbage(collectedCount) testHookCollectGarbage(collectedCount)
} }
case <-db.close: case <-db.close:
@ -243,6 +245,8 @@ func (db *DB) triggerGarbageCollection() {
// writeGCSizeDelay duration to avoid very frequent // writeGCSizeDelay duration to avoid very frequent
// database operations. // database operations.
func (db *DB) writeGCSizeWorker() { func (db *DB) writeGCSizeWorker() {
defer close(db.writeGCSizeWorkerDone)
for { for {
select { select {
case <-db.writeGCSizeTrigger: case <-db.writeGCSizeTrigger:

View File

@ -118,15 +118,6 @@ func testDB_collectGarbageWorker(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
}) })
// cleanup: drain the last testHookCollectGarbageChan
// element before calling deferred functions not to block
// collectGarbageWorker loop, preventing the race in
// setting testHookCollectGarbage function
select {
case <-testHookCollectGarbageChan:
default:
}
} }
// TestDB_collectGarbageWorker_withRequests is a helper test function // TestDB_collectGarbageWorker_withRequests is a helper test function
@ -290,6 +281,7 @@ func TestDB_gcSize(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer db.Close()
t.Run("gc index size", newIndexGCSizeTest(db)) t.Run("gc index size", newIndexGCSizeTest(db))

View File

@ -107,6 +107,12 @@ type DB struct {
// this channel is closed when close function is called // this channel is closed when close function is called
// to terminate other goroutines // to terminate other goroutines
close chan struct{} close chan struct{}
// protect Close method from exiting before
// garbage collection and gc size write workers
// are done
collectGarbageWorkerDone chan struct{}
writeGCSizeWorkerDone chan struct{}
} }
// Options struct holds optional parameters for configuring DB. // Options struct holds optional parameters for configuring DB.
@ -141,6 +147,8 @@ func New(path string, baseKey []byte, o *Options) (db *DB, err error) {
collectGarbageTrigger: make(chan struct{}, 1), collectGarbageTrigger: make(chan struct{}, 1),
writeGCSizeTrigger: make(chan struct{}, 1), writeGCSizeTrigger: make(chan struct{}, 1),
close: make(chan struct{}), close: make(chan struct{}),
collectGarbageWorkerDone: make(chan struct{}),
writeGCSizeWorkerDone: make(chan struct{}),
} }
if db.capacity <= 0 { if db.capacity <= 0 {
db.capacity = defaultCapacity db.capacity = defaultCapacity
@ -361,6 +369,21 @@ func New(path string, baseKey []byte, o *Options) (db *DB, err error) {
func (db *DB) Close() (err error) { func (db *DB) Close() (err error) {
close(db.close) close(db.close)
db.updateGCWG.Wait() db.updateGCWG.Wait()
// wait for gc worker and gc size write workers to
// return before closing the shed
timeout := time.After(5 * time.Second)
select {
case <-db.collectGarbageWorkerDone:
case <-timeout:
log.Error("localstore: collect garbage worker did not return after db close")
}
select {
case <-db.writeGCSizeWorkerDone:
case <-timeout:
log.Error("localstore: write gc size worker did not return after db close")
}
if err := db.writeGCSize(db.getGCSize()); err != nil { if err := db.writeGCSize(db.getGCSize()); err != nil {
log.Error("localstore: write gc size", "err", err) log.Error("localstore: write gc size", "err", err)
} }

View File

@ -163,6 +163,7 @@ func BenchmarkNew(b *testing.B) {
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
defer db.Close()
uploader := db.NewPutter(ModePutUpload) uploader := db.NewPutter(ModePutUpload)
syncer := db.NewSetter(ModeSetSync) syncer := db.NewSetter(ModeSetSync)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {

View File

@ -20,13 +20,11 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"os"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
// TestDB_SubscribePull uploads some chunks before and after // TestDB_SubscribePull uploads some chunks before and after
@ -34,12 +32,6 @@ import (
// all addresses are received in the right order // all addresses are received in the right order
// for expected proximity order bins. // for expected proximity order bins.
func TestDB_SubscribePull(t *testing.T) { func TestDB_SubscribePull(t *testing.T) {
if testutil.RaceEnabled && os.Getenv("TRAVIS") == "true" {
t.Skip("does not complete with -race on Travis")
// Note: related ticket TODO
}
db, cleanupFunc := newTestDB(t, nil) db, cleanupFunc := newTestDB(t, nil)
defer cleanupFunc() defer cleanupFunc()
@ -87,12 +79,6 @@ func TestDB_SubscribePull(t *testing.T) {
// validates if all addresses are received in the right order // validates if all addresses are received in the right order
// for expected proximity order bins. // for expected proximity order bins.
func TestDB_SubscribePull_multiple(t *testing.T) { func TestDB_SubscribePull_multiple(t *testing.T) {
if testutil.RaceEnabled && os.Getenv("TRAVIS") == "true" {
t.Skip("does not complete with -race on Travis")
// Note: related ticket TODO
}
db, cleanupFunc := newTestDB(t, nil) db, cleanupFunc := newTestDB(t, nil)
defer cleanupFunc() defer cleanupFunc()
@ -146,12 +132,6 @@ func TestDB_SubscribePull_multiple(t *testing.T) {
// and validates if all expected addresses are received in the // and validates if all expected addresses are received in the
// right order for expected proximity order bins. // right order for expected proximity order bins.
func TestDB_SubscribePull_since(t *testing.T) { func TestDB_SubscribePull_since(t *testing.T) {
if testutil.RaceEnabled && os.Getenv("TRAVIS") == "true" {
t.Skip("does not complete with -race on Travis")
// Note: related ticket TODO
}
db, cleanupFunc := newTestDB(t, nil) db, cleanupFunc := newTestDB(t, nil)
defer cleanupFunc() defer cleanupFunc()
@ -171,6 +151,9 @@ func TestDB_SubscribePull_since(t *testing.T) {
})() })()
uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) { uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) {
addrsMu.Lock()
defer addrsMu.Unlock()
last = make(map[uint8]ChunkDescriptor) last = make(map[uint8]ChunkDescriptor)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
chunk := generateRandomChunk() chunk := generateRandomChunk()
@ -182,7 +165,6 @@ func TestDB_SubscribePull_since(t *testing.T) {
bin := db.po(chunk.Address()) bin := db.po(chunk.Address())
addrsMu.Lock()
if _, ok := addrs[bin]; !ok { if _, ok := addrs[bin]; !ok {
addrs[bin] = make([]storage.Address, 0) addrs[bin] = make([]storage.Address, 0)
} }
@ -190,7 +172,6 @@ func TestDB_SubscribePull_since(t *testing.T) {
addrs[bin] = append(addrs[bin], chunk.Address()) addrs[bin] = append(addrs[bin], chunk.Address())
wantedChunksCount++ wantedChunksCount++
} }
addrsMu.Unlock()
lastTimestampMu.RLock() lastTimestampMu.RLock()
storeTimestamp := lastTimestamp storeTimestamp := lastTimestamp
@ -242,12 +223,6 @@ func TestDB_SubscribePull_since(t *testing.T) {
// and validates if all expected addresses are received in the // and validates if all expected addresses are received in the
// right order for expected proximity order bins. // right order for expected proximity order bins.
func TestDB_SubscribePull_until(t *testing.T) { func TestDB_SubscribePull_until(t *testing.T) {
if testutil.RaceEnabled && os.Getenv("TRAVIS") == "true" {
t.Skip("does not complete with -race on Travis")
// Note: related ticket TODO
}
db, cleanupFunc := newTestDB(t, nil) db, cleanupFunc := newTestDB(t, nil)
defer cleanupFunc() defer cleanupFunc()
@ -267,6 +242,9 @@ func TestDB_SubscribePull_until(t *testing.T) {
})() })()
uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) { uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) {
addrsMu.Lock()
defer addrsMu.Unlock()
last = make(map[uint8]ChunkDescriptor) last = make(map[uint8]ChunkDescriptor)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
chunk := generateRandomChunk() chunk := generateRandomChunk()
@ -278,7 +256,6 @@ func TestDB_SubscribePull_until(t *testing.T) {
bin := db.po(chunk.Address()) bin := db.po(chunk.Address())
addrsMu.Lock()
if _, ok := addrs[bin]; !ok { if _, ok := addrs[bin]; !ok {
addrs[bin] = make([]storage.Address, 0) addrs[bin] = make([]storage.Address, 0)
} }
@ -286,7 +263,6 @@ func TestDB_SubscribePull_until(t *testing.T) {
addrs[bin] = append(addrs[bin], chunk.Address()) addrs[bin] = append(addrs[bin], chunk.Address())
wantedChunksCount++ wantedChunksCount++
} }
addrsMu.Unlock()
lastTimestampMu.RLock() lastTimestampMu.RLock()
storeTimestamp := lastTimestamp storeTimestamp := lastTimestamp
@ -337,12 +313,6 @@ func TestDB_SubscribePull_until(t *testing.T) {
// and until arguments, and validates if all expected addresses // and until arguments, and validates if all expected addresses
// are received in the right order for expected proximity order bins. // are received in the right order for expected proximity order bins.
func TestDB_SubscribePull_sinceAndUntil(t *testing.T) { func TestDB_SubscribePull_sinceAndUntil(t *testing.T) {
if testutil.RaceEnabled && os.Getenv("TRAVIS") == "true" {
t.Skip("does not complete with -race on Travis")
// Note: related ticket TODO
}
db, cleanupFunc := newTestDB(t, nil) db, cleanupFunc := newTestDB(t, nil)
defer cleanupFunc() defer cleanupFunc()
@ -362,6 +332,9 @@ func TestDB_SubscribePull_sinceAndUntil(t *testing.T) {
})() })()
uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) { uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) {
addrsMu.Lock()
defer addrsMu.Unlock()
last = make(map[uint8]ChunkDescriptor) last = make(map[uint8]ChunkDescriptor)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
chunk := generateRandomChunk() chunk := generateRandomChunk()
@ -373,7 +346,6 @@ func TestDB_SubscribePull_sinceAndUntil(t *testing.T) {
bin := db.po(chunk.Address()) bin := db.po(chunk.Address())
addrsMu.Lock()
if _, ok := addrs[bin]; !ok { if _, ok := addrs[bin]; !ok {
addrs[bin] = make([]storage.Address, 0) addrs[bin] = make([]storage.Address, 0)
} }
@ -381,7 +353,6 @@ func TestDB_SubscribePull_sinceAndUntil(t *testing.T) {
addrs[bin] = append(addrs[bin], chunk.Address()) addrs[bin] = append(addrs[bin], chunk.Address())
wantedChunksCount++ wantedChunksCount++
} }
addrsMu.Unlock()
lastTimestampMu.RLock() lastTimestampMu.RLock()
storeTimestamp := lastTimestamp storeTimestamp := lastTimestamp
@ -442,6 +413,9 @@ func TestDB_SubscribePull_sinceAndUntil(t *testing.T) {
// uploadRandomChunksBin uploads random chunks to database and adds them to // uploadRandomChunksBin uploads random chunks to database and adds them to
// the map of addresses ber bin. // the map of addresses ber bin.
func uploadRandomChunksBin(t *testing.T, db *DB, uploader *Putter, addrs map[uint8][]storage.Address, addrsMu *sync.Mutex, wantedChunksCount *int, count int) { func uploadRandomChunksBin(t *testing.T, db *DB, uploader *Putter, addrs map[uint8][]storage.Address, addrsMu *sync.Mutex, wantedChunksCount *int, count int) {
addrsMu.Lock()
defer addrsMu.Unlock()
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
chunk := generateRandomChunk() chunk := generateRandomChunk()
@ -450,13 +424,11 @@ func uploadRandomChunksBin(t *testing.T, db *DB, uploader *Putter, addrs map[uin
t.Fatal(err) t.Fatal(err)
} }
addrsMu.Lock()
bin := db.po(chunk.Address()) bin := db.po(chunk.Address())
if _, ok := addrs[bin]; !ok { if _, ok := addrs[bin]; !ok {
addrs[bin] = make([]storage.Address, 0) addrs[bin] = make([]storage.Address, 0)
} }
addrs[bin] = append(addrs[bin], chunk.Address()) addrs[bin] = append(addrs[bin], chunk.Address())
addrsMu.Unlock()
*wantedChunksCount++ *wantedChunksCount++
} }
@ -473,19 +445,24 @@ func readPullSubscriptionBin(ctx context.Context, bin uint8, ch <-chan ChunkDesc
if !ok { if !ok {
return return
} }
var err error
addrsMu.Lock() addrsMu.Lock()
if i+1 > len(addrs[bin]) { if i+1 > len(addrs[bin]) {
errChan <- fmt.Errorf("got more chunk addresses %v, then expected %v, for bin %v", i+1, len(addrs[bin]), bin) err = fmt.Errorf("got more chunk addresses %v, then expected %v, for bin %v", i+1, len(addrs[bin]), bin)
} } else {
want := addrs[bin][i] want := addrs[bin][i]
addrsMu.Unlock()
var err error
if !bytes.Equal(got.Address, want) { if !bytes.Equal(got.Address, want) {
err = fmt.Errorf("got chunk address %v in bin %v %s, want %s", i, bin, got.Address.Hex(), want) err = fmt.Errorf("got chunk address %v in bin %v %s, want %s", i, bin, got.Address.Hex(), want)
} }
}
addrsMu.Unlock()
i++ i++
// send one and only one error per received address // send one and only one error per received address
errChan <- err select {
case errChan <- err:
case <-ctx.Done():
return
}
case <-ctx.Done(): case <-ctx.Done():
return return
} }

View File

@ -40,6 +40,9 @@ func TestDB_SubscribePush(t *testing.T) {
var chunksMu sync.Mutex var chunksMu sync.Mutex
uploadRandomChunks := func(count int) { uploadRandomChunks := func(count int) {
chunksMu.Lock()
defer chunksMu.Unlock()
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
chunk := generateRandomChunk() chunk := generateRandomChunk()
@ -48,9 +51,7 @@ func TestDB_SubscribePush(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
chunksMu.Lock()
chunks = append(chunks, chunk) chunks = append(chunks, chunk)
chunksMu.Unlock()
} }
} }
@ -90,7 +91,11 @@ func TestDB_SubscribePush(t *testing.T) {
} }
i++ i++
// send one and only one error per received address // send one and only one error per received address
errChan <- err select {
case errChan <- err:
case <-ctx.Done():
return
}
case <-ctx.Done(): case <-ctx.Done():
return return
} }
@ -123,6 +128,9 @@ func TestDB_SubscribePush_multiple(t *testing.T) {
var addrsMu sync.Mutex var addrsMu sync.Mutex
uploadRandomChunks := func(count int) { uploadRandomChunks := func(count int) {
addrsMu.Lock()
defer addrsMu.Unlock()
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
chunk := generateRandomChunk() chunk := generateRandomChunk()
@ -131,9 +139,7 @@ func TestDB_SubscribePush_multiple(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
addrsMu.Lock()
addrs = append(addrs, chunk.Address()) addrs = append(addrs, chunk.Address())
addrsMu.Unlock()
} }
} }
@ -175,7 +181,11 @@ func TestDB_SubscribePush_multiple(t *testing.T) {
} }
i++ i++
// send one and only one error per received address // send one and only one error per received address
errChan <- err select {
case errChan <- err:
case <-ctx.Done():
return
}
case <-ctx.Done(): case <-ctx.Done():
return return
} }