swarm: fix network/stream data races (#19051)

* swarm/network/stream: newStreamerTester cleanup only if err is nil

* swarm/network/stream: raise newStreamerTester waitForPeers timeout

* swarm/network/stream: fix data races in GetPeerSubscriptions

* swarm/storage: prevent data race on LDBStore.batchesC

https://github.com/ethersphere/go-ethereum/issues/1198#issuecomment-461775049

* swarm/network/stream: fix TestGetSubscriptionsRPC data race

https://github.com/ethersphere/go-ethereum/issues/1198#issuecomment-461768477

* swarm/network/stream: correctly use Simulation.Run callback

https://github.com/ethersphere/go-ethereum/issues/1198#issuecomment-461783804

* swarm/network: protect addrCountC in Kademlia.AddrCountC function

https://github.com/ethersphere/go-ethereum/issues/1198#issuecomment-462273444

* p2p/simulations: fix a deadlock calling getRandomNode with lock

https://github.com/ethersphere/go-ethereum/issues/1198#issuecomment-462317407

* swarm/network/stream: terminate disconnect goruotines in tests

* swarm/network/stream: reduce memory consumption when testing data races

* swarm/network/stream: add watchDisconnections helper function

* swarm/network/stream: add concurrent counter for tests

* swarm/network/stream: rename race/norace test files and use const

* swarm/network/stream: remove watchSim and its panic

* swarm/network/stream: pass context in watchDisconnections

* swarm/network/stream: add concurrent safe bool for watchDisconnections

* swarm/storage: fix LDBStore.batchesC data race by not closing it
This commit is contained in:
Janoš Guljaš 2019-02-13 13:03:23 +01:00 committed by Viktor Trón
parent d596bea2d5
commit 3fd6db2bf6
14 changed files with 274 additions and 197 deletions

View File

@ -461,7 +461,7 @@ func (net *Network) getRandomNode(ids []enode.ID, excludeIDs []enode.ID) *Node {
if l == 0 { if l == 0 {
return nil return nil
} }
return net.GetNode(filtered[rand.Intn(l)]) return net.getNode(filtered[rand.Intn(l)])
} }
func filterIDs(ids []enode.ID, excludeIDs []enode.ID) []enode.ID { func filterIDs(ids []enode.ID, excludeIDs []enode.ID) []enode.ID {

View File

@ -353,6 +353,9 @@ func (k *Kademlia) sendNeighbourhoodDepthChange() {
// Not receiving from the returned channel will block Register function // Not receiving from the returned channel will block Register function
// when address count value changes. // when address count value changes.
func (k *Kademlia) AddrCountC() <-chan int { func (k *Kademlia) AddrCountC() <-chan int {
k.lock.Lock()
defer k.lock.Unlock()
if k.addrCountC == nil { if k.addrCountC == nil {
k.addrCountC = make(chan int) k.addrCountC = make(chan int)
} }

View File

@ -151,7 +151,7 @@ func newStreamerTester(registryOptions *RegistryOptions) (*p2ptest.ProtocolTeste
// temp datadir // temp datadir
datadir, err := ioutil.TempDir("", "streamer") datadir, err := ioutil.TempDir("", "streamer")
if err != nil { if err != nil {
return nil, nil, nil, func() {}, err return nil, nil, nil, nil, err
} }
removeDataDir := func() { removeDataDir := func() {
os.RemoveAll(datadir) os.RemoveAll(datadir)
@ -163,12 +163,14 @@ func newStreamerTester(registryOptions *RegistryOptions) (*p2ptest.ProtocolTeste
localStore, err := storage.NewTestLocalStoreForAddr(params) localStore, err := storage.NewTestLocalStoreForAddr(params)
if err != nil { if err != nil {
return nil, nil, nil, removeDataDir, err removeDataDir()
return nil, nil, nil, nil, err
} }
netStore, err := storage.NewNetStore(localStore, nil) netStore, err := storage.NewNetStore(localStore, nil)
if err != nil { if err != nil {
return nil, nil, nil, removeDataDir, err removeDataDir()
return nil, nil, nil, nil, err
} }
delivery := NewDelivery(to, netStore) delivery := NewDelivery(to, netStore)
@ -180,8 +182,9 @@ func newStreamerTester(registryOptions *RegistryOptions) (*p2ptest.ProtocolTeste
} }
protocolTester := p2ptest.NewProtocolTester(addr.ID(), 1, streamer.runProtocol) protocolTester := p2ptest.NewProtocolTester(addr.ID(), 1, streamer.runProtocol)
err = waitForPeers(streamer, 1*time.Second, 1) err = waitForPeers(streamer, 10*time.Second, 1)
if err != nil { if err != nil {
teardown()
return nil, nil, nil, nil, errors.New("timeout: peer is not created") return nil, nil, nil, nil, errors.New("timeout: peer is not created")
} }
@ -317,3 +320,54 @@ func createTestLocalStorageForID(id enode.ID, addr *network.BzzAddr) (storage.Ch
} }
return store, datadir, nil return store, datadir, nil
} }
// watchDisconnections receives simulation peer events in a new goroutine and sets atomic value
// disconnected to true in case of a disconnect event.
func watchDisconnections(ctx context.Context, sim *simulation.Simulation) (disconnected *boolean) {
log.Debug("Watching for disconnections")
disconnections := sim.PeerEvents(
ctx,
sim.NodeIDs(),
simulation.NewPeerEventsFilter().Drop(),
)
disconnected = new(boolean)
go func() {
for {
select {
case <-ctx.Done():
return
case d := <-disconnections:
if d.Error != nil {
log.Error("peer drop event error", "node", d.NodeID, "peer", d.PeerID, "err", d.Error)
} else {
log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
}
disconnected.set(true)
}
}
}()
return disconnected
}
// boolean is used to concurrently set
// and read a boolean value.
type boolean struct {
v bool
mu sync.RWMutex
}
// set sets the value.
func (b *boolean) set(v bool) {
b.mu.Lock()
defer b.mu.Unlock()
b.v = v
}
// bool reads the value.
func (b *boolean) bool() bool {
b.mu.RLock()
defer b.mu.RUnlock()
return b.v
}

View File

@ -22,7 +22,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
@ -48,10 +47,10 @@ func TestStreamerRetrieveRequest(t *testing.T) {
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
} }
tester, streamer, _, teardown, err := newStreamerTester(regOpts) tester, streamer, _, teardown, err := newStreamerTester(regOpts)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
node := tester.Nodes[0] node := tester.Nodes[0]
@ -100,10 +99,10 @@ func TestStreamerUpstreamRetrieveRequestMsgExchangeWithoutStore(t *testing.T) {
Retrieval: RetrievalEnabled, Retrieval: RetrievalEnabled,
Syncing: SyncingDisabled, //do no syncing Syncing: SyncingDisabled, //do no syncing
}) })
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
node := tester.Nodes[0] node := tester.Nodes[0]
@ -172,10 +171,10 @@ func TestStreamerUpstreamRetrieveRequestMsgExchange(t *testing.T) {
Retrieval: RetrievalEnabled, Retrieval: RetrievalEnabled,
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
}) })
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
node := tester.Nodes[0] node := tester.Nodes[0]
@ -362,10 +361,10 @@ func TestStreamerDownstreamChunkDeliveryMsgExchange(t *testing.T) {
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
}) })
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) { streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) {
return &testClient{ return &testClient{
@ -485,7 +484,8 @@ func testDeliveryFromNodes(t *testing.T, nodes, chunkCount int, skipCheck bool)
} }
log.Info("Starting simulation") log.Info("Starting simulation")
ctx := context.Background() ctx, cancel := context.WithCancel(context.Background())
defer cancel()
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
nodeIDs := sim.UpNodeIDs() nodeIDs := sim.UpNodeIDs()
//determine the pivot node to be the first node of the simulation //determine the pivot node to be the first node of the simulation
@ -548,27 +548,10 @@ func testDeliveryFromNodes(t *testing.T, nodes, chunkCount int, skipCheck bool)
retErrC <- err retErrC <- err
}() }()
log.Debug("Watching for disconnections") disconnected := watchDisconnections(ctx, sim)
disconnections := sim.PeerEvents(
context.Background(),
sim.NodeIDs(),
simulation.NewPeerEventsFilter().Drop(),
)
var disconnected atomic.Value
go func() {
for d := range disconnections {
if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
disconnected.Store(true)
}
}
}()
defer func() { defer func() {
if err != nil { if err != nil && disconnected.bool() {
if yes, ok := disconnected.Load().(bool); ok && yes { err = errors.New("disconnect events received")
err = errors.New("disconnect events received")
}
} }
}() }()
@ -589,7 +572,7 @@ func testDeliveryFromNodes(t *testing.T, nodes, chunkCount int, skipCheck bool)
return fmt.Errorf("Test failed, chunks not available on all nodes") return fmt.Errorf("Test failed, chunks not available on all nodes")
} }
if err := <-retErrC; err != nil { if err := <-retErrC; err != nil {
t.Fatalf("requesting chunks: %v", err) return fmt.Errorf("requesting chunks: %v", err)
} }
log.Debug("Test terminated successfully") log.Debug("Test terminated successfully")
return nil return nil
@ -657,21 +640,22 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, chunkCount int, skipCheck b
b.Fatal(err) b.Fatal(err)
} }
ctx := context.Background() ctx, cancel := context.WithCancel(context.Background())
defer cancel()
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
nodeIDs := sim.UpNodeIDs() nodeIDs := sim.UpNodeIDs()
node := nodeIDs[len(nodeIDs)-1] node := nodeIDs[len(nodeIDs)-1]
item, ok := sim.NodeItem(node, bucketKeyFileStore) item, ok := sim.NodeItem(node, bucketKeyFileStore)
if !ok { if !ok {
b.Fatal("No filestore") return errors.New("No filestore")
} }
remoteFileStore := item.(*storage.FileStore) remoteFileStore := item.(*storage.FileStore)
pivotNode := nodeIDs[0] pivotNode := nodeIDs[0]
item, ok = sim.NodeItem(pivotNode, bucketKeyNetStore) item, ok = sim.NodeItem(pivotNode, bucketKeyNetStore)
if !ok { if !ok {
b.Fatal("No filestore") return errors.New("No filestore")
} }
netStore := item.(*storage.NetStore) netStore := item.(*storage.NetStore)
@ -679,26 +663,10 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, chunkCount int, skipCheck b
return err return err
} }
disconnections := sim.PeerEvents( disconnected := watchDisconnections(ctx, sim)
context.Background(),
sim.NodeIDs(),
simulation.NewPeerEventsFilter().Drop(),
)
var disconnected atomic.Value
go func() {
for d := range disconnections {
if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
disconnected.Store(true)
}
}
}()
defer func() { defer func() {
if err != nil { if err != nil && disconnected.bool() {
if yes, ok := disconnected.Load().(bool); ok && yes { err = errors.New("disconnect events received")
err = errors.New("disconnect events received")
}
} }
}() }()
// benchmark loop // benchmark loop
@ -713,12 +681,12 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, chunkCount int, skipCheck b
ctx := context.TODO() ctx := context.TODO()
hash, wait, err := remoteFileStore.Store(ctx, testutil.RandomReader(i, chunkSize), int64(chunkSize), false) hash, wait, err := remoteFileStore.Store(ctx, testutil.RandomReader(i, chunkSize), int64(chunkSize), false)
if err != nil { if err != nil {
b.Fatalf("expected no error. got %v", err) return fmt.Errorf("store: %v", err)
} }
// wait until all chunks stored // wait until all chunks stored
err = wait(ctx) err = wait(ctx)
if err != nil { if err != nil {
b.Fatalf("expected no error. got %v", err) return fmt.Errorf("wait store: %v", err)
} }
// collect the hashes // collect the hashes
hashes[i] = hash hashes[i] = hash
@ -754,10 +722,7 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, chunkCount int, skipCheck b
break Loop break Loop
} }
} }
if err != nil { return err
b.Fatal(err)
}
return nil
}) })
if result.Error != nil { if result.Error != nil {
b.Fatal(result.Error) b.Fatal(result.Error)

View File

@ -22,7 +22,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
@ -118,13 +117,11 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
_, wait, err := fileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false) _, wait, err := fileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false)
if err != nil { if err != nil {
log.Error("Store error: %v", "err", err) return fmt.Errorf("store: %v", err)
t.Fatal(err)
} }
err = wait(ctx) err = wait(ctx)
if err != nil { if err != nil {
log.Error("Wait error: %v", "err", err) return fmt.Errorf("wait store: %v", err)
t.Fatal(err)
} }
item, ok = sim.NodeItem(checker, bucketKeyRegistry) item, ok = sim.NodeItem(checker, bucketKeyRegistry)
@ -136,32 +133,15 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
liveErrC := make(chan error) liveErrC := make(chan error)
historyErrC := make(chan error) historyErrC := make(chan error)
log.Debug("Watching for disconnections")
disconnections := sim.PeerEvents(
context.Background(),
sim.NodeIDs(),
simulation.NewPeerEventsFilter().Drop(),
)
err = registry.Subscribe(storer, NewStream(externalStreamName, "", live), history, Top) err = registry.Subscribe(storer, NewStream(externalStreamName, "", live), history, Top)
if err != nil { if err != nil {
return err return err
} }
var disconnected atomic.Value disconnected := watchDisconnections(ctx, sim)
go func() {
for d := range disconnections {
if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
disconnected.Store(true)
}
}
}()
defer func() { defer func() {
if err != nil { if err != nil && disconnected.bool() {
if yes, ok := disconnected.Load().(bool); ok && yes { err = errors.New("disconnect events received")
err = errors.New("disconnect events received")
}
} }
}() }()

View File

@ -29,10 +29,10 @@ func TestLigthnodeRetrieveRequestWithRetrieve(t *testing.T) {
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
} }
tester, _, _, teardown, err := newStreamerTester(registryOptions) tester, _, _, teardown, err := newStreamerTester(registryOptions)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
node := tester.Nodes[0] node := tester.Nodes[0]
@ -68,10 +68,10 @@ func TestLigthnodeRetrieveRequestWithoutRetrieve(t *testing.T) {
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
} }
tester, _, _, teardown, err := newStreamerTester(registryOptions) tester, _, _, teardown, err := newStreamerTester(registryOptions)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
node := tester.Nodes[0] node := tester.Nodes[0]
@ -112,10 +112,10 @@ func TestLigthnodeRequestSubscriptionWithSync(t *testing.T) {
Syncing: SyncingRegisterOnly, Syncing: SyncingRegisterOnly,
} }
tester, _, _, teardown, err := newStreamerTester(registryOptions) tester, _, _, teardown, err := newStreamerTester(registryOptions)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
node := tester.Nodes[0] node := tester.Nodes[0]
@ -157,10 +157,10 @@ func TestLigthnodeRequestSubscriptionWithoutSync(t *testing.T) {
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
} }
tester, _, _, teardown, err := newStreamerTester(registryOptions) tester, _, _, teardown, err := newStreamerTester(registryOptions)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
node := tester.Nodes[0] node := tester.Nodes[0]

View File

@ -0,0 +1,24 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// +build !race
package stream
// Provide a flag to reduce the scope of tests when running them
// with race detector. Some of the tests are doing a lot of allocations
// on the heap, and race detector uses much more memory to track them.
const raceTest = false

View File

@ -0,0 +1,23 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// +build race
package stream
// Reduce the scope of some tests when running with race detector,
// as it raises the memory consumption significantly.
const raceTest = true

View File

@ -17,11 +17,12 @@ package stream
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"runtime" "runtime"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
@ -92,6 +93,15 @@ func TestSyncingViaGlobalSync(t *testing.T) {
if *longrunning { if *longrunning {
chnkCnt = []int{1, 8, 32, 256, 1024} chnkCnt = []int{1, 8, 32, 256, 1024}
nodeCnt = []int{16, 32, 64, 128, 256} nodeCnt = []int{16, 32, 64, 128, 256}
} else if raceTest {
// TestSyncingViaGlobalSync allocates a lot of memory
// with race detector. By reducing the number of chunks
// and nodes, memory consumption is lower and data races
// are still checked, while correctness of syncing is
// tested with more chunks and nodes in regular (!race)
// tests.
chnkCnt = []int{4}
nodeCnt = []int{16}
} else { } else {
//default test //default test
chnkCnt = []int{4, 32} chnkCnt = []int{4, 32}
@ -113,7 +123,23 @@ var simServiceMap = map[string]simulation.ServiceFunc{
return nil, nil, err return nil, nil, err
} }
r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ var dir string
var store *state.DBStore
if raceTest {
// Use on-disk DBStore to reduce memory consumption in race tests.
dir, err = ioutil.TempDir("", "swarm-stream-")
if err != nil {
return nil, nil, err
}
store, err = state.NewDBStore(dir)
if err != nil {
return nil, nil, err
}
} else {
store = state.NewInmemoryStore()
}
r := NewRegistry(addr.ID(), delivery, netStore, store, &RegistryOptions{
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingAutoSubscribe, Syncing: SyncingAutoSubscribe,
SyncUpdateDelay: 3 * time.Second, SyncUpdateDelay: 3 * time.Second,
@ -156,36 +182,24 @@ func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) {
t.Fatal(err) t.Fatal(err)
} }
disconnections := sim.PeerEvents(
context.Background(),
sim.NodeIDs(),
simulation.NewPeerEventsFilter().Drop(),
)
var disconnected atomic.Value
go func() {
for d := range disconnections {
if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
disconnected.Store(true)
}
}
}()
result := runSim(conf, ctx, sim, chunkCount) result := runSim(conf, ctx, sim, chunkCount)
if result.Error != nil { if result.Error != nil {
t.Fatal(result.Error) t.Fatal(result.Error)
} }
if yes, ok := disconnected.Load().(bool); ok && yes {
t.Fatal("disconnect events received")
}
log.Info("Simulation ended") log.Info("Simulation ended")
} }
func runSim(conf *synctestConfig, ctx context.Context, sim *simulation.Simulation, chunkCount int) simulation.Result { func runSim(conf *synctestConfig, ctx context.Context, sim *simulation.Simulation, chunkCount int) simulation.Result {
return sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { return sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
disconnected := watchDisconnections(ctx, sim)
defer func() {
if err != nil && disconnected.bool() {
err = errors.New("disconnect events received")
}
}()
nodeIDs := sim.UpNodeIDs() nodeIDs := sim.UpNodeIDs()
for _, n := range nodeIDs { for _, n := range nodeIDs {
//get the kademlia overlay address from this ID //get the kademlia overlay address from this ID

View File

@ -939,16 +939,22 @@ It returns a map of node IDs with an array of string representations of Stream o
func (api *API) GetPeerSubscriptions() map[string][]string { func (api *API) GetPeerSubscriptions() map[string][]string {
//create the empty map //create the empty map
pstreams := make(map[string][]string) pstreams := make(map[string][]string)
//iterate all streamer peers //iterate all streamer peers
api.streamer.peersMu.RLock()
defer api.streamer.peersMu.RUnlock()
for id, p := range api.streamer.peers { for id, p := range api.streamer.peers {
var streams []string var streams []string
//every peer has a map of stream servers //every peer has a map of stream servers
//every stream server represents a subscription //every stream server represents a subscription
p.serverMu.RLock()
for s := range p.servers { for s := range p.servers {
//append the string representation of the stream //append the string representation of the stream
//to the list for this peer //to the list for this peer
streams = append(streams, s.String()) streams = append(streams, s.String())
} }
p.serverMu.RUnlock()
//set the array of stream servers to the map //set the array of stream servers to the map
pstreams[id.String()] = streams pstreams[id.String()] = streams
} }

View File

@ -41,10 +41,10 @@ import (
func TestStreamerSubscribe(t *testing.T) { func TestStreamerSubscribe(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
stream := NewStream("foo", "", true) stream := NewStream("foo", "", true)
err = streamer.Subscribe(tester.Nodes[0].ID(), stream, NewRange(0, 0), Top) err = streamer.Subscribe(tester.Nodes[0].ID(), stream, NewRange(0, 0), Top)
@ -55,10 +55,10 @@ func TestStreamerSubscribe(t *testing.T) {
func TestStreamerRequestSubscription(t *testing.T) { func TestStreamerRequestSubscription(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
stream := NewStream("foo", "", false) stream := NewStream("foo", "", false)
err = streamer.RequestSubscription(tester.Nodes[0].ID(), stream, &Range{}, Top) err = streamer.RequestSubscription(tester.Nodes[0].ID(), stream, &Range{}, Top)
@ -146,10 +146,10 @@ func (self *testServer) Close() {
func TestStreamerDownstreamSubscribeUnsubscribeMsgExchange(t *testing.T) { func TestStreamerDownstreamSubscribeUnsubscribeMsgExchange(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) { streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) {
return newTestClient(t), nil return newTestClient(t), nil
@ -239,10 +239,10 @@ func TestStreamerDownstreamSubscribeUnsubscribeMsgExchange(t *testing.T) {
func TestStreamerUpstreamSubscribeUnsubscribeMsgExchange(t *testing.T) { func TestStreamerUpstreamSubscribeUnsubscribeMsgExchange(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
stream := NewStream("foo", "", false) stream := NewStream("foo", "", false)
@ -306,10 +306,10 @@ func TestStreamerUpstreamSubscribeUnsubscribeMsgExchange(t *testing.T) {
func TestStreamerUpstreamSubscribeUnsubscribeMsgExchangeLive(t *testing.T) { func TestStreamerUpstreamSubscribeUnsubscribeMsgExchangeLive(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
stream := NewStream("foo", "", true) stream := NewStream("foo", "", true)
@ -372,10 +372,10 @@ func TestStreamerUpstreamSubscribeUnsubscribeMsgExchangeLive(t *testing.T) {
func TestStreamerUpstreamSubscribeErrorMsgExchange(t *testing.T) { func TestStreamerUpstreamSubscribeErrorMsgExchange(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) {
return newTestServer(t, 0), nil return newTestServer(t, 0), nil
@ -416,10 +416,10 @@ func TestStreamerUpstreamSubscribeErrorMsgExchange(t *testing.T) {
func TestStreamerUpstreamSubscribeLiveAndHistory(t *testing.T) { func TestStreamerUpstreamSubscribeLiveAndHistory(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
stream := NewStream("foo", "", true) stream := NewStream("foo", "", true)
@ -479,10 +479,10 @@ func TestStreamerUpstreamSubscribeLiveAndHistory(t *testing.T) {
func TestStreamerDownstreamCorruptHashesMsgExchange(t *testing.T) { func TestStreamerDownstreamCorruptHashesMsgExchange(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
stream := NewStream("foo", "", true) stream := NewStream("foo", "", true)
@ -544,10 +544,10 @@ func TestStreamerDownstreamCorruptHashesMsgExchange(t *testing.T) {
func TestStreamerDownstreamOfferedHashesMsgExchange(t *testing.T) { func TestStreamerDownstreamOfferedHashesMsgExchange(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
stream := NewStream("foo", "", true) stream := NewStream("foo", "", true)
@ -643,10 +643,10 @@ func TestStreamerDownstreamOfferedHashesMsgExchange(t *testing.T) {
func TestStreamerRequestSubscriptionQuitMsgExchange(t *testing.T) { func TestStreamerRequestSubscriptionQuitMsgExchange(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(nil) tester, streamer, _, teardown, err := newStreamerTester(nil)
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) {
return newTestServer(t, 10), nil return newTestServer(t, 10), nil
@ -780,10 +780,10 @@ func TestMaxPeerServersWithUnsubscribe(t *testing.T) {
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
MaxPeerServers: maxPeerServers, MaxPeerServers: maxPeerServers,
}) })
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) {
return newTestServer(t, 0), nil return newTestServer(t, 0), nil
@ -854,10 +854,10 @@ func TestMaxPeerServersWithoutUnsubscribe(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(&RegistryOptions{ tester, streamer, _, teardown, err := newStreamerTester(&RegistryOptions{
MaxPeerServers: maxPeerServers, MaxPeerServers: maxPeerServers,
}) })
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) {
return newTestServer(t, 0), nil return newTestServer(t, 0), nil
@ -940,10 +940,10 @@ func TestHasPriceImplementation(t *testing.T) {
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
}) })
defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer teardown()
if r.prices == nil { if r.prices == nil {
t.Fatal("No prices implementation available for the stream protocol") t.Fatal("No prices implementation available for the stream protocol")
@ -1177,6 +1177,7 @@ starts the simulation, waits for SyncUpdateDelay in order to kick off
stream registration, then tests that there are subscriptions. stream registration, then tests that there are subscriptions.
*/ */
func TestGetSubscriptionsRPC(t *testing.T) { func TestGetSubscriptionsRPC(t *testing.T) {
// arbitrarily set to 4 // arbitrarily set to 4
nodeCount := 4 nodeCount := 4
// run with more nodes if `longrunning` flag is set // run with more nodes if `longrunning` flag is set
@ -1188,19 +1189,16 @@ func TestGetSubscriptionsRPC(t *testing.T) {
// holds the msg code for SubscribeMsg // holds the msg code for SubscribeMsg
var subscribeMsgCode uint64 var subscribeMsgCode uint64
var ok bool var ok bool
var expectedMsgCount = 0 var expectedMsgCount counter
// this channel signalizes that the expected amount of subscriptiosn is done // this channel signalizes that the expected amount of subscriptiosn is done
allSubscriptionsDone := make(chan struct{}) allSubscriptionsDone := make(chan struct{})
lock := sync.RWMutex{}
// after the test, we need to reset the subscriptionFunc to the default // after the test, we need to reset the subscriptionFunc to the default
defer func() { subscriptionFunc = doRequestSubscription }() defer func() { subscriptionFunc = doRequestSubscription }()
// we use this subscriptionFunc for this test: just increases count and calls the actual subscription // we use this subscriptionFunc for this test: just increases count and calls the actual subscription
subscriptionFunc = func(r *Registry, p *network.Peer, bin uint8, subs map[enode.ID]map[Stream]struct{}) bool { subscriptionFunc = func(r *Registry, p *network.Peer, bin uint8, subs map[enode.ID]map[Stream]struct{}) bool {
lock.Lock() expectedMsgCount.inc()
expectedMsgCount++
lock.Unlock()
doRequestSubscription(r, p, bin, subs) doRequestSubscription(r, p, bin, subs)
return true return true
} }
@ -1290,24 +1288,24 @@ func TestGetSubscriptionsRPC(t *testing.T) {
select { select {
case <-allSubscriptionsDone: case <-allSubscriptionsDone:
case <-ctx.Done(): case <-ctx.Done():
t.Fatal("Context timed out") return errors.New("Context timed out")
} }
log.Debug("Expected message count: ", "expectedMsgCount", expectedMsgCount) log.Debug("Expected message count: ", "expectedMsgCount", expectedMsgCount.count())
//now iterate again, this time we call each node via RPC to get its subscriptions //now iterate again, this time we call each node via RPC to get its subscriptions
realCount := 0 realCount := 0
for _, node := range nodes { for _, node := range nodes {
//create rpc client //create rpc client
client, err := node.Client() client, err := node.Client()
if err != nil { if err != nil {
t.Fatalf("create node 1 rpc client fail: %v", err) return fmt.Errorf("create node 1 rpc client fail: %v", err)
} }
//ask it for subscriptions //ask it for subscriptions
pstreams := make(map[string][]string) pstreams := make(map[string][]string)
err = client.Call(&pstreams, "stream_getPeerSubscriptions") err = client.Call(&pstreams, "stream_getPeerSubscriptions")
if err != nil { if err != nil {
t.Fatal(err) return fmt.Errorf("client call stream_getPeerSubscriptions: %v", err)
} }
//length of the subscriptions can not be smaller than number of peers //length of the subscriptions can not be smaller than number of peers
log.Debug("node subscriptions", "node", node.String()) log.Debug("node subscriptions", "node", node.String())
@ -1324,8 +1322,9 @@ func TestGetSubscriptionsRPC(t *testing.T) {
} }
} }
// every node is mutually subscribed to each other, so the actual count is half of it // every node is mutually subscribed to each other, so the actual count is half of it
if realCount/2 != expectedMsgCount { emc := expectedMsgCount.count()
return fmt.Errorf("Real subscriptions and expected amount don't match; real: %d, expected: %d", realCount/2, expectedMsgCount) if realCount/2 != emc {
return fmt.Errorf("Real subscriptions and expected amount don't match; real: %d, expected: %d", realCount/2, emc)
} }
return nil return nil
}) })
@ -1333,3 +1332,26 @@ func TestGetSubscriptionsRPC(t *testing.T) {
t.Fatal(result.Error) t.Fatal(result.Error)
} }
} }
// counter is used to concurrently increment
// and read an integer value.
type counter struct {
v int
mu sync.RWMutex
}
// Increment the counter.
func (c *counter) inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.v++
}
// Read the counter value.
func (c *counter) count() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.v
}

View File

@ -22,8 +22,8 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math" "math"
"os"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
@ -44,9 +44,15 @@ const dataChunkCount = 200
func TestSyncerSimulation(t *testing.T) { func TestSyncerSimulation(t *testing.T) {
testSyncBetweenNodes(t, 2, dataChunkCount, true, 1) testSyncBetweenNodes(t, 2, dataChunkCount, true, 1)
testSyncBetweenNodes(t, 4, dataChunkCount, true, 1) // This test uses much more memory when running with
testSyncBetweenNodes(t, 8, dataChunkCount, true, 1) // race detector. Allow it to finish successfully by
testSyncBetweenNodes(t, 16, dataChunkCount, true, 1) // reducing its scope, and still check for data races
// with the smallest number of nodes.
if !raceTest {
testSyncBetweenNodes(t, 4, dataChunkCount, true, 1)
testSyncBetweenNodes(t, 8, dataChunkCount, true, 1)
testSyncBetweenNodes(t, 16, dataChunkCount, true, 1)
}
} }
func createMockStore(globalStore mock.GlobalStorer, id enode.ID, addr *network.BzzAddr) (lstore storage.ChunkStore, datadir string, err error) { func createMockStore(globalStore mock.GlobalStorer, id enode.ID, addr *network.BzzAddr) (lstore storage.ChunkStore, datadir string, err error) {
@ -80,7 +86,23 @@ func testSyncBetweenNodes(t *testing.T, nodes, chunkCount int, skipCheck bool, p
return nil, nil, err return nil, nil, err
} }
r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ var dir string
var store *state.DBStore
if raceTest {
// Use on-disk DBStore to reduce memory consumption in race tests.
dir, err = ioutil.TempDir("", "swarm-stream-")
if err != nil {
return nil, nil, err
}
store, err = state.NewDBStore(dir)
if err != nil {
return nil, nil, err
}
} else {
store = state.NewInmemoryStore()
}
r := NewRegistry(addr.ID(), delivery, netStore, store, &RegistryOptions{
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingAutoSubscribe, Syncing: SyncingAutoSubscribe,
SkipCheck: skipCheck, SkipCheck: skipCheck,
@ -89,6 +111,9 @@ func testSyncBetweenNodes(t *testing.T, nodes, chunkCount int, skipCheck bool, p
cleanup = func() { cleanup = func() {
r.Close() r.Close()
clean() clean()
if dir != "" {
os.RemoveAll(dir)
}
} }
return r, cleanup, nil return r, cleanup, nil
@ -114,26 +139,10 @@ func testSyncBetweenNodes(t *testing.T, nodes, chunkCount int, skipCheck bool, p
nodeIndex[id] = i nodeIndex[id] = i
} }
disconnections := sim.PeerEvents( disconnected := watchDisconnections(ctx, sim)
context.Background(),
sim.NodeIDs(),
simulation.NewPeerEventsFilter().Drop(),
)
var disconnected atomic.Value
go func() {
for d := range disconnections {
if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
disconnected.Store(true)
}
}
}()
defer func() { defer func() {
if err != nil { if err != nil && disconnected.bool() {
if yes, ok := disconnected.Load().(bool); ok && yes { err = errors.New("disconnect events received")
err = errors.New("disconnect events received")
}
} }
}() }()
@ -142,7 +151,7 @@ func testSyncBetweenNodes(t *testing.T, nodes, chunkCount int, skipCheck bool, p
id := nodeIDs[j] id := nodeIDs[j]
client, err := sim.Net.GetNode(id).Client() client, err := sim.Net.GetNode(id).Client()
if err != nil { if err != nil {
t.Fatal(err) return fmt.Errorf("node %s client: %v", id, err)
} }
sid := nodeIDs[j+1] sid := nodeIDs[j+1]
client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream("SYNC", FormatSyncBinKey(1), false), NewRange(0, 0), Top) client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream("SYNC", FormatSyncBinKey(1), false), NewRange(0, 0), Top)
@ -158,7 +167,7 @@ func testSyncBetweenNodes(t *testing.T, nodes, chunkCount int, skipCheck bool, p
size := chunkCount * chunkSize size := chunkCount * chunkSize
_, wait, err := fileStore.Store(ctx, testutil.RandomReader(j, size), int64(size), false) _, wait, err := fileStore.Store(ctx, testutil.RandomReader(j, size), int64(size), false)
if err != nil { if err != nil {
t.Fatal(err.Error()) return fmt.Errorf("fileStore.Store: %v", err)
} }
wait(ctx) wait(ctx)
} }
@ -273,7 +282,7 @@ func TestSameVersionID(t *testing.T) {
//the peers should connect, thus getting the peer should not return nil //the peers should connect, thus getting the peer should not return nil
if registry.getPeer(nodes[1]) == nil { if registry.getPeer(nodes[1]) == nil {
t.Fatal("Expected the peer to not be nil, but it is") return errors.New("Expected the peer to not be nil, but it is")
} }
return nil return nil
}) })
@ -338,7 +347,7 @@ func TestDifferentVersionID(t *testing.T) {
//getting the other peer should fail due to the different version numbers //getting the other peer should fail due to the different version numbers
if registry.getPeer(nodes[1]) != nil { if registry.getPeer(nodes[1]) != nil {
t.Fatal("Expected the peer to be nil, but it is not") return errors.New("Expected the peer to be nil, but it is not")
} }
return nil return nil
}) })

View File

@ -66,31 +66,6 @@ func setupSim(serviceMap map[string]simulation.ServiceFunc) (int, int, *simulati
return nodeCount, chunkCount, sim return nodeCount, chunkCount, sim
} }
//watch for disconnections and wait for healthy
func watchSim(sim *simulation.Simulation) (context.Context, context.CancelFunc) {
ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
if _, err := sim.WaitTillHealthy(ctx); err != nil {
panic(err)
}
disconnections := sim.PeerEvents(
context.Background(),
sim.NodeIDs(),
simulation.NewPeerEventsFilter().Drop(),
)
go func() {
for d := range disconnections {
log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
panic("unexpected disconnect")
cancelSimRun()
}
}()
return ctx, cancelSimRun
}
//This test requests bogus hashes into the network //This test requests bogus hashes into the network
func TestNonExistingHashesWithServer(t *testing.T) { func TestNonExistingHashesWithServer(t *testing.T) {
@ -102,19 +77,25 @@ func TestNonExistingHashesWithServer(t *testing.T) {
panic(err) panic(err)
} }
ctx, cancelSimRun := watchSim(sim)
defer cancelSimRun()
//in order to get some meaningful visualization, it is beneficial //in order to get some meaningful visualization, it is beneficial
//to define a minimum duration of this test //to define a minimum duration of this test
testDuration := 20 * time.Second testDuration := 20 * time.Second
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
disconnected := watchDisconnections(ctx, sim)
defer func() {
if err != nil {
if yes, ok := disconnected.Load().(bool); ok && yes {
err = errors.New("disconnect events received")
}
}
}()
//check on the node's FileStore (netstore) //check on the node's FileStore (netstore)
id := sim.Net.GetRandomUpNode().ID() id := sim.Net.GetRandomUpNode().ID()
item, ok := sim.NodeItem(id, bucketKeyFileStore) item, ok := sim.NodeItem(id, bucketKeyFileStore)
if !ok { if !ok {
t.Fatalf("No filestore") return errors.New("No filestore")
} }
fileStore := item.(*storage.FileStore) fileStore := item.(*storage.FileStore)
//create a bogus hash //create a bogus hash
@ -213,9 +194,6 @@ func TestSnapshotSyncWithServer(t *testing.T) {
panic(err) panic(err)
} }
ctx, cancelSimRun := watchSim(sim)
defer cancelSimRun()
//run the sim //run the sim
result := runSim(conf, ctx, sim, chunkCount) result := runSim(conf, ctx, sim, chunkCount)

View File

@ -1049,7 +1049,6 @@ func (s *LDBStore) Close() {
s.lock.Unlock() s.lock.Unlock()
// force writing out current batch // force writing out current batch
s.writeCurrentBatch() s.writeCurrentBatch()
close(s.batchesC)
s.db.Close() s.db.Close()
} }