swarm/network/stream: fix TestSyncerSimulation deadlock

This change fixes the synchronization between simulations network
shutdown and calling of rpc subscription Unsubscribe method.
If the unsubscribe is called after net shutdown, rpc client is
blocking.

This change also addresses the panic in benchmarkDeliveryFromNodes
function, when executing benchmarks from tests.
This commit is contained in:
Janos Guljas 2018-04-11 14:28:44 +02:00
parent f6d48fe564
commit 2605f59bc0
5 changed files with 99 additions and 18 deletions

View file

@ -22,6 +22,7 @@ import (
crand "crypto/rand" crand "crypto/rand"
"fmt" "fmt"
"io" "io"
"sync"
"testing" "testing"
"time" "time"
@ -318,7 +319,11 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
} }
sim, teardown, err := streamTesting.NewSimulation(conf) sim, teardown, err := streamTesting.NewSimulation(conf)
defer teardown() var rpcSubscriptionsWg sync.WaitGroup
defer func() {
rpcSubscriptionsWg.Wait()
teardown()
}()
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
@ -347,6 +352,7 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
errc := make(chan error, 1) errc := make(chan error, 1)
waitPeerErrC = make(chan error) waitPeerErrC = make(chan error)
quitC := make(chan struct{}) quitC := make(chan struct{})
defer close(quitC)
action := func(ctx context.Context) error { action := func(ctx context.Context) error {
// each node Subscribes to each other's swarmChunkServerStreamName // each node Subscribes to each other's swarmChunkServerStreamName
@ -369,10 +375,15 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
for j := 0; j < nodes-1; j++ { for j := 0; j < nodes-1; j++ {
id := sim.IDs[j] id := sim.IDs[j]
err := sim.CallClient(id, func(client *rpc.Client) error { err := sim.CallClient(id, func(client *rpc.Client) error {
err := streamTesting.WatchDisconnections(id, client, errc, quitC) doneC, err := streamTesting.WatchDisconnections(id, client, errc, quitC)
if err != nil { if err != nil {
return err return err
} }
rpcSubscriptionsWg.Add(1)
go func() {
<-doneC
rpcSubscriptionsWg.Done()
}()
ctx, cancel := context.WithTimeout(ctx, 1*time.Second) ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel() defer cancel()
sid := sim.IDs[j+1] sid := sim.IDs[j+1]
@ -475,6 +486,8 @@ func BenchmarkDeliveryFromNodesWithCheck(b *testing.B) {
func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skipCheck bool) { func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skipCheck bool) {
defaultSkipCheck = skipCheck defaultSkipCheck = skipCheck
toAddr = network.NewAddrFromNodeID toAddr = network.NewAddrFromNodeID
createStoreFunc = createTestLocalStorageFromSim
registries = make(map[discover.NodeID]*TestRegistry)
timeout := 300 * time.Second timeout := 300 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
@ -489,7 +502,11 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
EnableMsgEvents: false, EnableMsgEvents: false,
} }
sim, teardown, err := streamTesting.NewSimulation(conf) sim, teardown, err := streamTesting.NewSimulation(conf)
defer teardown() var rpcSubscriptionsWg sync.WaitGroup
defer func() {
rpcSubscriptionsWg.Wait()
teardown()
}()
if err != nil { if err != nil {
b.Fatal(err.Error()) b.Fatal(err.Error())
} }
@ -539,10 +556,15 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
for j := 0; j < nodes-1; j++ { for j := 0; j < nodes-1; j++ {
id := sim.IDs[j] id := sim.IDs[j]
err = sim.CallClient(id, func(client *rpc.Client) error { err = sim.CallClient(id, func(client *rpc.Client) error {
err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC) doneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC)
if err != nil { if err != nil {
return err return err
} }
rpcSubscriptionsWg.Add(1)
go func() {
<-doneC
rpcSubscriptionsWg.Done()
}()
ctx, cancel := context.WithTimeout(ctx, 1*time.Second) ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel() defer cancel()
sid := sim.IDs[j+1] // the upstream peer's id sid := sim.IDs[j+1] // the upstream peer's id

View file

@ -22,6 +22,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"sync"
"testing" "testing"
"time" "time"
@ -90,7 +91,11 @@ func testIntervals(t *testing.T, live bool, history *Range) {
} }
sim, teardown, err := streamTesting.NewSimulation(conf) sim, teardown, err := streamTesting.NewSimulation(conf)
defer teardown() var rpcSubscriptionsWg sync.WaitGroup
defer func() {
rpcSubscriptionsWg.Wait()
teardown()
}()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -136,10 +141,15 @@ func testIntervals(t *testing.T, live bool, history *Range) {
sid := sim.IDs[0] sid := sim.IDs[0]
err := streamTesting.WatchDisconnections(id, client, errc, quitC) doneC, err := streamTesting.WatchDisconnections(id, client, errc, quitC)
if err != nil { if err != nil {
return err return err
} }
rpcSubscriptionsWg.Add(1)
go func() {
<-doneC
rpcSubscriptionsWg.Done()
}()
ctx, cancel := context.WithTimeout(ctx, 100*time.Second) ctx, cancel := context.WithTimeout(ctx, 100*time.Second)
defer cancel() defer cancel()

View file

@ -199,15 +199,20 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
if err != nil { if err != nil {
return err return err
} }
var rpcSubscriptionsWg sync.WaitGroup
//do cleanup after test is terminated //do cleanup after test is terminated
defer func() { defer func() {
// close quitC channel to signall all goroutines to clanup
// before calling simulation network shutdown.
close(quitC)
//wait for all rpc subscriptions to unsubscribe
rpcSubscriptionsWg.Wait()
//shutdown the snapshot network //shutdown the snapshot network
net.Shutdown() net.Shutdown()
//after the test, clean up local stores initialized with createLocalStoreForId //after the test, clean up local stores initialized with createLocalStoreForId
localStoreCleanup() localStoreCleanup()
//finally clear all data directories //finally clear all data directories
datadirsCleanup() datadirsCleanup()
close(quitC)
}() }()
//get the nodes of the network //get the nodes of the network
nodes := net.GetNodes() nodes := net.GetNodes()
@ -290,7 +295,15 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
return err return err
} }
watchSubscriptionEvents(ctx, id, client, errc) wsDoneC := watchSubscriptionEvents(ctx, id, client, errc, quitC)
// doneC is nil, the error happened which is sent to errc channel, already
if wsDoneC == nil {
continue
}
go func() {
<-wsDoneC
rpcSubscriptionsWg.Done()
}()
if log.Lvl(*loglevel) >= log.LvlTrace { if log.Lvl(*loglevel) >= log.LvlTrace {
//this will print the kademlia tables of all nodes //this will print the kademlia tables of all nodes
@ -306,10 +319,15 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
log.Debug(kt) log.Debug(kt)
} }
//watch for peers disconnecting //watch for peers disconnecting
err = streamTesting.WatchDisconnections(id, client, disconnectC, quitC) wdDoneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC)
if err != nil { if err != nil {
return err return err
} }
rpcSubscriptionsWg.Add(1)
go func() {
<-wdDoneC
rpcSubscriptionsWg.Done()
}()
//start syncing! //start syncing!
err = client.CallContext(ctx, nil, "stream_startSyncing") err = client.CallContext(ctx, nil, "stream_startSyncing")
if err != nil { if err != nil {
@ -589,7 +607,7 @@ func initNetWithSnapshot(nodeCount int) (*simulations.Network, error) {
//we want to wait for subscriptions to be established before uploading to test //we want to wait for subscriptions to be established before uploading to test
//that live syncing is working correctly //that live syncing is working correctly
func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rpc.Client, errc chan error) { func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rpc.Client, errc chan error, quitC chan struct{}) (doneC <-chan struct{}) {
events := make(chan *p2p.PeerEvent) events := make(chan *p2p.PeerEvent)
sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents") sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
if err != nil { if err != nil {
@ -597,11 +615,18 @@ func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rp
errc <- fmt.Errorf("error getting peer events for node %v: %s", id, err) errc <- fmt.Errorf("error getting peer events for node %v: %s", id, err)
return return
} }
c := make(chan struct{})
go func() { go func() {
defer sub.Unsubscribe() defer func() {
log.Trace("watch subscription events: unsubscribe", "id", id)
sub.Unsubscribe()
close(c)
}()
for { for {
select { select {
case <-quitC:
return
case <-ctx.Done(): case <-ctx.Done():
errc <- ctx.Err() errc <- ctx.Err()
return return
@ -618,7 +643,7 @@ func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rp
} }
} }
}() }()
return return c
} }
//create a local store for the given node //create a local store for the given node

View file

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"io" "io"
"math" "math"
"sync"
"testing" "testing"
"time" "time"
@ -69,7 +70,11 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
// create simulation network with the config // create simulation network with the config
sim, teardown, err := streamTesting.NewSimulation(conf) sim, teardown, err := streamTesting.NewSimulation(conf)
defer teardown() var rpcSubscriptionsWg sync.WaitGroup
defer func() {
rpcSubscriptionsWg.Wait()
teardown()
}()
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
@ -154,10 +159,15 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
id := sim.IDs[j] id := sim.IDs[j]
err := sim.CallClient(id, func(client *rpc.Client) error { err := sim.CallClient(id, func(client *rpc.Client) error {
// report disconnect events to the error channel cos peers should not disconnect // report disconnect events to the error channel cos peers should not disconnect
err := streamTesting.WatchDisconnections(id, client, errc, quitC) doneC, err := streamTesting.WatchDisconnections(id, client, errc, quitC)
if err != nil { if err != nil {
return err return err
} }
rpcSubscriptionsWg.Add(1)
go func() {
<-doneC
rpcSubscriptionsWg.Done()
}()
ctx, cancel := context.WithTimeout(ctx, 1*time.Second) ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel() defer cancel()
// start syncing, i.e., subscribe to upstream peers po 1 bin // start syncing, i.e., subscribe to upstream peers po 1 bin

View file

@ -143,7 +143,9 @@ func NewSimulation(conf *RunConfig) (*Simulation, func(), error) {
DefaultService: defaultService, DefaultService: defaultService,
}) })
teardown := func() { teardown := func() {
log.Trace("simulation: teardown adapter")
adapterTeardown() adapterTeardown()
log.Trace("simulation: teardown net")
net.Shutdown() net.Shutdown()
} }
ids := make([]discover.NodeID, nodes) ids := make([]discover.NodeID, nodes)
@ -162,8 +164,11 @@ func NewSimulation(conf *RunConfig) (*Simulation, func(), error) {
// set nodes number of Stores available // set nodes number of Stores available
stores, storeTeardown, err := SetStores(addrs...) stores, storeTeardown, err := SetStores(addrs...)
teardown = func() { teardown = func() {
log.Trace("simulation: teardown net")
net.Shutdown() net.Shutdown()
log.Trace("simulation: teardown adapter")
adapterTeardown() adapterTeardown()
log.Trace("simulation: teardown store")
storeTeardown() storeTeardown()
} }
if err != nil { if err != nil {
@ -216,14 +221,23 @@ func (s *Simulation) Run(ctx context.Context, conf *RunConfig) (*simulations.Ste
return result, nil return result, nil
} }
func WatchDisconnections(id discover.NodeID, client *rpc.Client, errc chan error, quitC chan struct{}) error { // WatchDisconnections subscribes to admin peerEvents and sends peer event drop
// errors to the errc channel. Channel quitC signals the termination of the event loop.
// Returned doneC will be closed after the rpc subscription is unsubscribed,
// signaling that simulations network is safe to shutdown.
func WatchDisconnections(id discover.NodeID, client *rpc.Client, errc chan error, quitC chan struct{}) (doneC <-chan struct{}, err error) {
events := make(chan *p2p.PeerEvent) events := make(chan *p2p.PeerEvent)
sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents") sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
if err != nil { if err != nil {
return fmt.Errorf("error getting peer events for node %v: %s", id, err) return nil, fmt.Errorf("error getting peer events for node %v: %s", id, err)
} }
c := make(chan struct{})
go func() { go func() {
defer sub.Unsubscribe() defer func() {
log.Trace("watch disconnections: unsubscribe", "id", id)
sub.Unsubscribe()
close(c)
}()
for { for {
select { select {
case <-quitC: case <-quitC:
@ -247,7 +261,7 @@ func WatchDisconnections(id discover.NodeID, client *rpc.Client, errc chan error
} }
} }
}() }()
return nil return c, nil
} }
func Trigger(d time.Duration, quitC chan struct{}, ids ...discover.NodeID) chan discover.NodeID { func Trigger(d time.Duration, quitC chan struct{}, ids ...discover.NodeID) chan discover.NodeID {