p2p/simulations: validate all connections on loading a snapshot

This commit is contained in:
Janos Guljas 2018-11-30 19:01:54 +01:00
parent 6fbdcb27f0
commit 49ca919900

View file

@ -20,7 +20,9 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"strings"
"sync" "sync"
"time" "time"
@ -707,6 +709,7 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn
// Load loads a network snapshot // Load loads a network snapshot
func (net *Network) Load(snap *Snapshot) error { func (net *Network) Load(snap *Snapshot) error {
// Start nodes.
for _, n := range snap.Nodes { for _, n := range snap.Nodes {
if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil { if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil {
return err return err
@ -718,6 +721,78 @@ func (net *Network) Load(snap *Snapshot) error {
return err return err
} }
} }
// Prepare connection events counter.
allConnected := make(chan struct{}) // closed when all connections are established
eventLoopStarted := make(chan struct{}) // ensures that event loop is started before it is closed
done := make(chan struct{}) // ensures that the event loop goroutine is terminated
defer close(done)
go func() {
// Subscribe to event channel.
events := make(chan *Event)
sub := net.Events().Subscribe(events)
defer sub.Unsubscribe()
// Expected number of connections.
total := len(snap.Conns)
// counter tracks the current number of connections.
var counter int
// once is a closed channel that is read in the event loop below
// only once.
// It ensures that eventLoopStarted is closed which signals that
// it is safe to call connect method on the network without the
// possibility to miss a few first connection events.
once := make(chan struct{})
// Close once channel so that it can be read from in the event loop.
close(once)
for {
select {
case e := <-events:
// Detect only connection events.
if e.Type != EventTypeConn {
continue
}
// Detect only "connect" events of all connection events.
if !e.Conn.Up {
continue
}
// Check that the connection is from the snapshot.
for _, conn := range snap.Conns {
if conn.One == e.Conn.One && conn.Other == e.Conn.Other {
counter++
if counter == total {
// Signal that all nodes are connected.
close(allConnected)
return
}
break
}
}
case <-once:
// Set once to nil as nil channel never blocks forever.
// This ensures that this for loop never gets into this part
// of the code again.
once = nil
// Proceed with connecting the nodes, as we are ready to
// detect events.
close(eventLoopStarted)
case <-done:
// Load function returned, terminate this goroutine.
return
}
}
}()
// Do not proceed until the goroutine with the event loop actually is ready
// to receive events.
<-eventLoopStarted
// Start connecting.
for _, conn := range snap.Conns { for _, conn := range snap.Conns {
if !net.GetNode(conn.One).Up || !net.GetNode(conn.Other).Up { if !net.GetNode(conn.One).Up || !net.GetNode(conn.Other).Up {
@ -725,10 +800,18 @@ func (net *Network) Load(snap *Snapshot) error {
//so it would result in the snapshot `Load` to fail //so it would result in the snapshot `Load` to fail
continue continue
} }
if err := net.Connect(conn.One, conn.Other); err != nil { if err := net.Connect(conn.One, conn.Other); err != nil && !strings.Contains(err.Error(), "already connected") {
return err return err
} }
} }
select {
// Wait until all connections from the snapshot are established.
case <-allConnected:
// Make sure that we do not wait forever.
case <-time.After(120 * time.Second):
return errors.New("snapshot connections not established")
}
return nil return nil
} }