mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
Merge pull request #385 from ethersphere/stream-test-rpc-unsubscribe-deadlock
Stream test RPC unsubscribe deadlock
This commit is contained in:
commit
b84f10bf75
5 changed files with 103 additions and 19 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,15 +200,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()
|
||||||
|
|
@ -292,7 +297,16 @@ 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
|
||||||
|
}
|
||||||
|
rpcSubscriptionsWg.Add(1)
|
||||||
|
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
|
||||||
|
|
@ -308,10 +322,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 {
|
||||||
|
|
@ -588,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 {
|
||||||
|
|
@ -596,13 +615,23 @@ 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()
|
select {
|
||||||
|
case errc <- ctx.Err():
|
||||||
|
case <-quitC:
|
||||||
|
}
|
||||||
return
|
return
|
||||||
case e := <-events:
|
case e := <-events:
|
||||||
//just catch SubscribeMsg
|
//just catch SubscribeMsg
|
||||||
|
|
@ -611,12 +640,16 @@ func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rp
|
||||||
}
|
}
|
||||||
case err := <-sub.Err():
|
case err := <-sub.Err():
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- fmt.Errorf("error getting peer events for node %v: %v", id, err)
|
select {
|
||||||
|
case errc <- fmt.Errorf("error getting peer events for node %v: %v", id, err):
|
||||||
|
case <-quitC:
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
//create a local store for the given node
|
//create a local store for the given node
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -216,14 +216,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 +256,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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue