提交 95d58b2a 作者: Juan Batiz-Benet

core: cleaned up bootstrap process

上级 dd9c1b62
...@@ -3,6 +3,8 @@ package core ...@@ -3,6 +3,8 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"math/rand" "math/rand"
"sync" "sync"
"time" "time"
...@@ -18,6 +20,7 @@ import ( ...@@ -18,6 +20,7 @@ import (
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
goprocess "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess" goprocess "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
procctx "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/context"
periodicproc "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic" periodicproc "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic"
) )
...@@ -25,128 +28,116 @@ import ( ...@@ -25,128 +28,116 @@ import (
// peers to bootstrap correctly. // peers to bootstrap correctly.
var ErrNotEnoughBootstrapPeers = errors.New("not enough bootstrap peers to bootstrap") var ErrNotEnoughBootstrapPeers = errors.New("not enough bootstrap peers to bootstrap")
const ( // BootstrapConfig specifies parameters used in an IpfsNode's network
// BootstrapPeriod governs the periodic interval at which the node will // bootstrapping process.
// attempt to bootstrap. The bootstrap process is not very expensive, so type BootstrapConfig struct {
// this threshold can afford to be small (<=30s).
BootstrapPeriod = 30 * time.Second
// BootstrapPeerThreshold governs the node Bootstrap process. If the node // MinPeerThreshold governs whether to bootstrap more connections. If the
// has less open connections than this number, it will open connections // node has less open connections than this number, it will open connections
// to the bootstrap nodes. From there, the routing system should be able // to the bootstrap nodes. From there, the routing system should be able
// to use the connections to the bootstrap nodes to connect to even more // to use the connections to the bootstrap nodes to connect to even more
// peers. Routing systems like the IpfsDHT do so in their own Bootstrap // peers. Routing systems like the IpfsDHT do so in their own Bootstrap
// process, which issues random queries to find more peers. // process, which issues random queries to find more peers.
BootstrapPeerThreshold = 4 MinPeerThreshold int
// Period governs the periodic interval at which the node will
// attempt to bootstrap. The bootstrap process is not very expensive, so
// this threshold can afford to be small (<=30s).
Period time.Duration
// BootstrapConnectionTimeout determines how long to wait for a bootstrap // ConnectionTimeout determines how long to wait for a bootstrap
// connection attempt before cancelling it. // connection attempt before cancelling it.
BootstrapConnectionTimeout time.Duration = BootstrapPeriod / 3 ConnectionTimeout time.Duration
)
// BootstrapPeers is a function that returns a set of bootstrap peers
// for the bootstrap process to use. This makes it possible for clients
// to control the peers the process uses at any moment.
BootstrapPeers func() []peer.PeerInfo
}
// nodeBootstrapper is a small object used to bootstrap an IpfsNode. // DefaultBootstrapConfig specifies default sane parameters for bootstrapping.
type nodeBootstrapper struct { var DefaultBootstrapConfig = BootstrapConfig{
node *IpfsNode MinPeerThreshold: 4,
Period: 30 * time.Second,
ConnectionTimeout: (30 * time.Second) / 3, // Perod / 3
} }
// TryToBootstrap starts IpfsNode bootstrapping. This function will run an func BootstrapConfigWithPeers(pis []peer.PeerInfo) BootstrapConfig {
// initial bootstrapping phase before exiting: connect to several bootstrap cfg := DefaultBootstrapConfig
// nodes. This allows callers to call this function synchronously to: cfg.BootstrapPeers = func() []peer.PeerInfo {
// - check if an error occurrs (bootstrapping unsuccessful) return pis
// - wait before starting services which require the node to be bootstrapped }
// return cfg
// If bootstrapping initially fails, Bootstrap() will try again for a total of }
// three times, before giving up completely. Note that in environments where a
// node may be initialized offline, as normal operation, BootstrapForever() // Bootstrap kicks off IpfsNode bootstrapping. This function will periodically
// should be used instead. // check the number of open connections and -- if there are too few -- initiate
// // connections to well-known bootstrap peers. It also kicks off subsystem
// Note: this function could be much cleaner if we were to relax the constraint // bootstrapping (i.e. routing).
// that we want to exit **after** we have performed initial bootstrapping (and are func Bootstrap(n *IpfsNode, cfg BootstrapConfig) (io.Closer, error) {
// thus connected to nodes). The constraint may not be that useful in practice.
// Consider cases when we initialize the node while disconnected from the internet.
// We don't want this launch to fail... want to continue launching the node, hoping
// that bootstrapping will work in the future if we get connected.
func (nb *nodeBootstrapper) TryToBootstrap(ctx context.Context, peers []peer.PeerInfo) error {
n := nb.node
// TODO what bootstrapping should happen if there is no DHT? i.e. we could // TODO what bootstrapping should happen if there is no DHT? i.e. we could
// continue connecting to our bootstrap peers, but for what purpose? for now // continue connecting to our bootstrap peers, but for what purpose? for now
// simply exit without connecting to any of them. When we introduce another // simply exit without connecting to any of them. When we introduce another
// routing system that uses bootstrap peers we can change this. // routing system that uses bootstrap peers we can change this.
dht, ok := n.Routing.(*dht.IpfsDHT) thedht, ok := n.Routing.(*dht.IpfsDHT)
if !ok { if !ok {
return nil return ioutil.NopCloser(nil), nil
} }
for i := 0; i < 3; i++ { // the periodic bootstrap function -- the connection supervisor
if err := bootstrapRound(ctx, n.PeerHost, dht, n.Peerstore, peers); err != nil { periodic := func(worker goprocess.Process) {
return err ctx := procctx.WithProcessClosing(context.Background(), worker)
defer log.EventBegin(ctx, "periodicBootstrap", n.Identity).Done()
if err := bootstrapRound(ctx, n.PeerHost, thedht, n.Peerstore, cfg); err != nil {
log.Event(ctx, "bootstrapError", n.Identity, lgbl.Error(err))
log.Errorf("%s bootstrap error: %s", n.Identity, err)
} }
} }
// at this point we have done at least one round of initial bootstrap. // kick off the node's periodic bootstrapping
// we're ready to kick off dht bootstrapping. proc := periodicproc.Tick(cfg.Period, periodic)
dbproc, err := dht.Bootstrap(ctx) proc.Go(periodic) // run one right now.
// kick off dht bootstrapping.
dbproc, err := thedht.Bootstrap(dht.DefaultBootstrapConfig)
if err != nil { if err != nil {
return err proc.Close()
return nil, err
} }
// kick off the node's periodic bootstrapping
proc := periodicproc.Tick(BootstrapPeriod, func(worker goprocess.Process) {
defer log.EventBegin(ctx, "periodicBootstrap", n.Identity).Done()
if err := bootstrapRound(ctx, n.PeerHost, dht, n.Peerstore, peers); err != nil {
log.Error(err)
}
})
// add dht bootstrap proc as a child, so it is closed automatically when we are. // add dht bootstrap proc as a child, so it is closed automatically when we are.
proc.AddChild(dbproc) proc.AddChild(dbproc)
return proc, nil
// we were given a context. instead of returning proc for the caller
// to manage, for now we just close the proc when context is done.
go func() {
<-ctx.Done()
proc.Close()
}()
return nil
}
// BootstrapForever starts IpfsNode bootstrapping. Unlike TryToBootstrap(),
// BootstrapForever() will run indefinitely (until its context is cancelled).
// This is particularly useful for the daemon and other services, which may
// be started offline and will come online at a future date.
//
// TODO: check offline --to--> online case works well and doesn't hurt perf.
// We may still be dialing. We should check network config.
func (nb *nodeBootstrapper) BootstrapForever(ctx context.Context, peers []peer.PeerInfo) error {
for {
if err := nb.TryToBootstrap(ctx, peers); err == nil {
return nil
}
}
} }
func bootstrapRound(ctx context.Context, func bootstrapRound(ctx context.Context,
host host.Host, host host.Host,
route *dht.IpfsDHT, route *dht.IpfsDHT,
peerstore peer.Peerstore, peerstore peer.Peerstore,
bootstrapPeers []peer.PeerInfo) error { cfg BootstrapConfig) error {
ctx, _ = context.WithTimeout(ctx, cfg.ConnectionTimeout)
id := host.ID()
ctx, _ = context.WithTimeout(ctx, BootstrapConnectionTimeout) // get bootstrap peers from config. retrieving them here makes
// sure we remain observant of changes to client configuration.
peers := cfg.BootstrapPeers()
// determine how many bootstrap connections to open // determine how many bootstrap connections to open
connectedPeers := host.Network().Peers() connected := host.Network().Peers()
if len(connectedPeers) >= BootstrapPeerThreshold { if len(connected) >= cfg.MinPeerThreshold {
log.Event(ctx, "bootstrapSkip", host.ID()) log.Event(ctx, "bootstrapSkip", id)
log.Debugf("%s core bootstrap skipped -- connected to %d (> %d) nodes", log.Debugf("%s core bootstrap skipped -- connected to %d (> %d) nodes",
host.ID(), len(connectedPeers), BootstrapPeerThreshold) id, len(connected), cfg.MinPeerThreshold)
return nil return nil
} }
numCxnsToCreate := BootstrapPeerThreshold - len(connectedPeers) numToDial := cfg.MinPeerThreshold - len(connected)
// filter out bootstrap nodes we are already connected to // filter out bootstrap nodes we are already connected to
var notConnected []peer.PeerInfo var notConnected []peer.PeerInfo
for _, p := range bootstrapPeers { for _, p := range peers {
if host.Network().Connectedness(p.ID) != inet.Connected { if host.Network().Connectedness(p.ID) != inet.Connected {
notConnected = append(notConnected, p) notConnected = append(notConnected, p)
} }
...@@ -154,17 +145,16 @@ func bootstrapRound(ctx context.Context, ...@@ -154,17 +145,16 @@ func bootstrapRound(ctx context.Context,
// if connected to all bootstrap peer candidates, exit // if connected to all bootstrap peer candidates, exit
if len(notConnected) < 1 { if len(notConnected) < 1 {
log.Debugf("%s no more bootstrap peers to create %d connections", host.ID(), numCxnsToCreate) log.Debugf("%s no more bootstrap peers to create %d connections", id, numToDial)
return ErrNotEnoughBootstrapPeers return ErrNotEnoughBootstrapPeers
} }
// connect to a random susbset of bootstrap candidates // connect to a random susbset of bootstrap candidates
randomSubset := randomSubsetOfPeers(notConnected, numCxnsToCreate) randSubset := randomSubsetOfPeers(notConnected, numToDial)
defer log.EventBegin(ctx, "bootstrapStart", host.ID()).Done()
log.Debugf("%s bootstrapping to %d nodes: %s", host.ID(), numCxnsToCreate, randomSubset) defer log.EventBegin(ctx, "bootstrapStart", id).Done()
if err := bootstrapConnect(ctx, peerstore, route, randomSubset); err != nil { log.Debugf("%s bootstrapping to %d nodes: %s", id, numToDial, randSubset)
log.Event(ctx, "bootstrapError", host.ID(), lgbl.Error(err)) if err := bootstrapConnect(ctx, peerstore, route, randSubset); err != nil {
log.Errorf("%s bootstrap error: %s", host.ID(), err)
return err return err
} }
return nil return nil
...@@ -196,12 +186,12 @@ func bootstrapConnect(ctx context.Context, ...@@ -196,12 +186,12 @@ func bootstrapConnect(ctx context.Context,
ps.AddAddresses(p.ID, p.Addrs) ps.AddAddresses(p.ID, p.Addrs)
err := route.Connect(ctx, p.ID) err := route.Connect(ctx, p.ID)
if err != nil { if err != nil {
log.Event(ctx, "bootstrapFailed", p.ID) log.Event(ctx, "bootstrapDialFailed", p.ID)
log.Errorf("failed to bootstrap with %v: %s", p.ID, err) log.Errorf("failed to bootstrap with %v: %s", p.ID, err)
errs <- err errs <- err
return return
} }
log.Event(ctx, "bootstrapSuccess", p.ID) log.Event(ctx, "bootstrapDialSuccess", p.ID)
log.Infof("bootstrapped with %v", p.ID) log.Infof("bootstrapped with %v", p.ID)
}(p) }(p)
} }
...@@ -223,7 +213,19 @@ func bootstrapConnect(ctx context.Context, ...@@ -223,7 +213,19 @@ func bootstrapConnect(ctx context.Context,
return nil return nil
} }
func toPeer(bootstrap config.BootstrapPeer) (p peer.PeerInfo, err error) { func toPeerInfos(bpeers []config.BootstrapPeer) ([]peer.PeerInfo, error) {
var peers []peer.PeerInfo
for _, bootstrap := range bpeers {
p, err := toPeerInfo(bootstrap)
if err != nil {
return nil, err
}
peers = append(peers, p)
}
return peers, nil
}
func toPeerInfo(bootstrap config.BootstrapPeer) (p peer.PeerInfo, err error) {
id, err := peer.IDB58Decode(bootstrap.PeerID) id, err := peer.IDB58Decode(bootstrap.PeerID)
if err != nil { if err != nil {
return return
......
...@@ -11,33 +11,36 @@ import ( ...@@ -11,33 +11,36 @@ import (
datastore "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore" datastore "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
debugerror "github.com/jbenet/go-ipfs/util/debugerror"
diag "github.com/jbenet/go-ipfs/diagnostics"
ic "github.com/jbenet/go-ipfs/p2p/crypto"
p2phost "github.com/jbenet/go-ipfs/p2p/host"
p2pbhost "github.com/jbenet/go-ipfs/p2p/host/basic"
swarm "github.com/jbenet/go-ipfs/p2p/net/swarm"
addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr"
peer "github.com/jbenet/go-ipfs/p2p/peer"
routing "github.com/jbenet/go-ipfs/routing"
dht "github.com/jbenet/go-ipfs/routing/dht"
offroute "github.com/jbenet/go-ipfs/routing/offline"
bstore "github.com/jbenet/go-ipfs/blocks/blockstore" bstore "github.com/jbenet/go-ipfs/blocks/blockstore"
bserv "github.com/jbenet/go-ipfs/blockservice" bserv "github.com/jbenet/go-ipfs/blockservice"
diag "github.com/jbenet/go-ipfs/diagnostics"
exchange "github.com/jbenet/go-ipfs/exchange" exchange "github.com/jbenet/go-ipfs/exchange"
bitswap "github.com/jbenet/go-ipfs/exchange/bitswap" bitswap "github.com/jbenet/go-ipfs/exchange/bitswap"
bsnet "github.com/jbenet/go-ipfs/exchange/bitswap/network" bsnet "github.com/jbenet/go-ipfs/exchange/bitswap/network"
offline "github.com/jbenet/go-ipfs/exchange/offline" offline "github.com/jbenet/go-ipfs/exchange/offline"
rp "github.com/jbenet/go-ipfs/exchange/reprovide" rp "github.com/jbenet/go-ipfs/exchange/reprovide"
mount "github.com/jbenet/go-ipfs/fuse/mount" mount "github.com/jbenet/go-ipfs/fuse/mount"
merkledag "github.com/jbenet/go-ipfs/merkledag" merkledag "github.com/jbenet/go-ipfs/merkledag"
namesys "github.com/jbenet/go-ipfs/namesys" namesys "github.com/jbenet/go-ipfs/namesys"
ic "github.com/jbenet/go-ipfs/p2p/crypto"
p2phost "github.com/jbenet/go-ipfs/p2p/host"
p2pbhost "github.com/jbenet/go-ipfs/p2p/host/basic"
swarm "github.com/jbenet/go-ipfs/p2p/net/swarm"
addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr"
peer "github.com/jbenet/go-ipfs/p2p/peer"
path "github.com/jbenet/go-ipfs/path" path "github.com/jbenet/go-ipfs/path"
pin "github.com/jbenet/go-ipfs/pin" pin "github.com/jbenet/go-ipfs/pin"
repo "github.com/jbenet/go-ipfs/repo" repo "github.com/jbenet/go-ipfs/repo"
config "github.com/jbenet/go-ipfs/repo/config" config "github.com/jbenet/go-ipfs/repo/config"
routing "github.com/jbenet/go-ipfs/routing"
dht "github.com/jbenet/go-ipfs/routing/dht"
offroute "github.com/jbenet/go-ipfs/routing/offline"
eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
debugerror "github.com/jbenet/go-ipfs/util/debugerror"
lgbl "github.com/jbenet/go-ipfs/util/eventlog/loggables"
) )
const IpnsValidatorTag = "ipns" const IpnsValidatorTag = "ipns"
...@@ -75,13 +78,14 @@ type IpfsNode struct { ...@@ -75,13 +78,14 @@ type IpfsNode struct {
Resolver *path.Resolver // the path resolution system Resolver *path.Resolver // the path resolution system
// Online // Online
PrivateKey ic.PrivKey // the local node's private Key PrivateKey ic.PrivKey // the local node's private Key
PeerHost p2phost.Host // the network host (server+client) PeerHost p2phost.Host // the network host (server+client)
Routing routing.IpfsRouting // the routing system. recommend ipfs-dht Bootstrapper io.Closer // the periodic bootstrapper
Exchange exchange.Interface // the block exchange + strategy (bitswap) Routing routing.IpfsRouting // the routing system. recommend ipfs-dht
Namesys namesys.NameSystem // the name system, resolves paths to hashes Exchange exchange.Interface // the block exchange + strategy (bitswap)
Diagnostics *diag.Diagnostics // the diagnostics service Namesys namesys.NameSystem // the name system, resolves paths to hashes
Reprovider *rp.Reprovider // the value reprovider system Diagnostics *diag.Diagnostics // the diagnostics service
Reprovider *rp.Reprovider // the value reprovider system
ctxgroup.ContextGroup ctxgroup.ContextGroup
...@@ -238,14 +242,7 @@ func (n *IpfsNode) StartOnlineServices(ctx context.Context) error { ...@@ -238,14 +242,7 @@ func (n *IpfsNode) StartOnlineServices(ctx context.Context) error {
n.Reprovider = rp.NewReprovider(n.Routing, n.Blockstore) n.Reprovider = rp.NewReprovider(n.Routing, n.Blockstore)
go n.Reprovider.ProvideEvery(ctx, kReprovideFrequency) go n.Reprovider.ProvideEvery(ctx, kReprovideFrequency)
// prepare bootstrap peers from config return n.Bootstrap(DefaultBootstrapConfig)
bpeers, err := n.loadBootstrapPeers()
if err != nil {
log.Event(ctx, "bootstrapError", n.Identity, lgbl.Error(err))
log.Errorf("%s bootstrap error: %s", n.Identity, err)
return debugerror.Wrap(err)
}
return n.Bootstrap(ctx, bpeers)
} }
// teardown closes owned children. If any errors occur, this function returns // teardown closes owned children. If any errors occur, this function returns
...@@ -254,20 +251,20 @@ func (n *IpfsNode) teardown() error { ...@@ -254,20 +251,20 @@ func (n *IpfsNode) teardown() error {
// owned objects are closed in this teardown to ensure that they're closed // owned objects are closed in this teardown to ensure that they're closed
// regardless of which constructor was used to add them to the node. // regardless of which constructor was used to add them to the node.
var closers []io.Closer var closers []io.Closer
if n.Repo != nil { addCloser := func(c io.Closer) {
closers = append(closers, n.Repo) if c != nil {
} closers = append(closers, c)
if n.Blocks != nil {
closers = append(closers, n.Blocks)
}
if n.Routing != nil {
if dht, ok := n.Routing.(*dht.IpfsDHT); ok {
closers = append(closers, dht)
} }
} }
if n.PeerHost != nil {
closers = append(closers, n.PeerHost) addCloser(n.Bootstrapper)
addCloser(n.Repo)
addCloser(n.Blocks)
if dht, ok := n.Routing.(*dht.IpfsDHT); ok {
addCloser(dht)
} }
addCloser(n.PeerHost)
var errs []error var errs []error
for _, closer := range closers { for _, closer := range closers {
if err := closer.Close(); err != nil { if err := closer.Close(); err != nil {
...@@ -293,16 +290,34 @@ func (n *IpfsNode) Resolve(path string) (*merkledag.Node, error) { ...@@ -293,16 +290,34 @@ func (n *IpfsNode) Resolve(path string) (*merkledag.Node, error) {
return n.Resolver.ResolvePath(path) return n.Resolver.ResolvePath(path)
} }
// Bootstrap is undefined when node is not in OnlineMode func (n *IpfsNode) Bootstrap(cfg BootstrapConfig) error {
func (n *IpfsNode) Bootstrap(ctx context.Context, peers []peer.PeerInfo) error {
// TODO what should return value be when in offlineMode? // TODO what should return value be when in offlineMode?
if n.Routing == nil { if n.Routing == nil {
return nil return nil
} }
nb := nodeBootstrapper{n} if n.Bootstrapper != nil {
return nb.TryToBootstrap(ctx, peers) n.Bootstrapper.Close() // stop previous bootstrap process.
}
// if the caller did not specify a bootstrap peer function, get the
// freshest bootstrap peers from config. this responds to live changes.
if cfg.BootstrapPeers == nil {
cfg.BootstrapPeers = func() []peer.PeerInfo {
bpeers := n.Repo.Config().Bootstrap
ps, err := toPeerInfos(bpeers)
if err != nil {
log.Error("failed to parse bootstrap peers from config: %s", bpeers)
return nil
}
return ps
}
}
var err error
n.Bootstrapper, err = Bootstrap(n, cfg)
return err
} }
func (n *IpfsNode) loadID() error { func (n *IpfsNode) loadID() error {
...@@ -342,18 +357,6 @@ func (n *IpfsNode) loadPrivateKey() error { ...@@ -342,18 +357,6 @@ func (n *IpfsNode) loadPrivateKey() error {
return nil return nil
} }
func (n *IpfsNode) loadBootstrapPeers() ([]peer.PeerInfo, error) {
var peers []peer.PeerInfo
for _, bootstrap := range n.Repo.Config().Bootstrap {
p, err := toPeer(bootstrap)
if err != nil {
return nil, err
}
peers = append(peers, p)
}
return peers, nil
}
// SetupOfflineRouting loads the local nodes private key and // SetupOfflineRouting loads the local nodes private key and
// uses it to instantiate a routing system in offline mode. // uses it to instantiate a routing system in offline mode.
// This is primarily used for offline ipns modifications. // This is primarily used for offline ipns modifications.
......
...@@ -17,52 +17,42 @@ import ( ...@@ -17,52 +17,42 @@ import (
periodicproc "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic" periodicproc "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic"
) )
// DefaultBootstrapQueries specifies how many queries to run, // BootstrapConfig specifies parameters used bootstrapping the DHT.
// if the user does not specify a different number as an option.
// //
// For now, this is set to 16 queries, which is an aggressive number. // Note there is a tradeoff between the bootstrap period and the
// We are currently more interested in ensuring we have a properly formed // number of queries. We could support a higher period with less
// DHT than making sure our dht minimizes traffic. Once we are more certain // queries.
// of our implementation's robustness, we should lower this down to 8 or 4. type BootstrapConfig struct {
// Queries int // how many queries to run per period
// Note there is also a tradeoff between the bootstrap period and the number Period time.Duration // how often to run periodi cbootstrap.
// of queries. We could support a higher period with a smaller number of Timeout time.Duration // how long to wait for a bootstrao query to run
// queries }
const DefaultBootstrapQueries = 1
// DefaultBootstrapPeriod specifies how often to periodically run bootstrap, var DefaultBootstrapConfig = BootstrapConfig{
// if the user does not specify a different number as an option. // For now, this is set to 1 query.
// // We are currently more interested in ensuring we have a properly formed
// For now, this is set to 10 seconds, which is an aggressive period. We are // DHT than making sure our dht minimizes traffic. Once we are more certain
// We are currently more interested in ensuring we have a properly formed // of our implementation's robustness, we should lower this down to 8 or 4.
// DHT than making sure our dht minimizes traffic. Once we are more certain Queries: 1,
// implementation's robustness, we should lower this down to 30s or 1m.
//
// Note there is also a tradeoff between the bootstrap period and the number
// of queries. We could support a higher period with a smaller number of
// queries
const DefaultBootstrapPeriod = time.Duration(10 * time.Second)
// DefaultBootstrapTimeout specifies how long to wait for a bootstrap query
// to run.
const DefaultBootstrapTimeout = time.Duration(10 * time.Second)
// Bootstrap runs bootstrapping once, then calls SignalBootstrap with default
// parameters: DefaultBootstrapQueries and DefaultBootstrapPeriod. This allows
// the user to catch an error off the bat if the connections are faulty. It also
// allows BootstrapOnSignal not to run bootstrap at the beginning, which is useful
// for instrumenting it on tests, or delaying bootstrap until the network is online
// and connected to at least a few nodes.
//
// Like PeriodicBootstrap, Bootstrap returns a process, so the user can stop it.
func (dht *IpfsDHT) Bootstrap(ctx context.Context) (goprocess.Process, error) {
if err := dht.runBootstrap(ctx, DefaultBootstrapQueries); err != nil { // For now, this is set to 10 seconds, which is an aggressive period. We are
return nil, err // We are currently more interested in ensuring we have a properly formed
} // DHT than making sure our dht minimizes traffic. Once we are more certain
// implementation's robustness, we should lower this down to 30s or 1m.
Period: time.Duration(20 * time.Second),
sig := time.Tick(DefaultBootstrapPeriod) Timeout: time.Duration(20 * time.Second),
return dht.BootstrapOnSignal(DefaultBootstrapQueries, sig) }
// Bootstrap ensures the dht routing table remains healthy as peers come and go.
// it builds up a list of peers by requesting random peer IDs. The Bootstrap
// process will run a number of queries each time, and run every time signal fires.
// These parameters are configurable.
//
// Bootstrap returns a process, so the user can stop it.
func (dht *IpfsDHT) Bootstrap(config BootstrapConfig) (goprocess.Process, error) {
sig := time.Tick(config.Period)
return dht.BootstrapOnSignal(config, sig)
} }
// SignalBootstrap ensures the dht routing table remains healthy as peers come and go. // SignalBootstrap ensures the dht routing table remains healthy as peers come and go.
...@@ -71,9 +61,9 @@ func (dht *IpfsDHT) Bootstrap(ctx context.Context) (goprocess.Process, error) { ...@@ -71,9 +61,9 @@ func (dht *IpfsDHT) Bootstrap(ctx context.Context) (goprocess.Process, error) {
// These parameters are configurable. // These parameters are configurable.
// //
// SignalBootstrap returns a process, so the user can stop it. // SignalBootstrap returns a process, so the user can stop it.
func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (goprocess.Process, error) { func (dht *IpfsDHT) BootstrapOnSignal(cfg BootstrapConfig, signal <-chan time.Time) (goprocess.Process, error) {
if queries <= 0 { if cfg.Queries <= 0 {
return nil, fmt.Errorf("invalid number of queries: %d", queries) return nil, fmt.Errorf("invalid number of queries: %d", cfg.Queries)
} }
if signal == nil { if signal == nil {
...@@ -85,27 +75,9 @@ func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (gop ...@@ -85,27 +75,9 @@ func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (gop
// maybe this is a good case for whole module event pub/sub? // maybe this is a good case for whole module event pub/sub?
ctx := dht.Context() ctx := dht.Context()
if err := dht.runBootstrap(ctx, queries); err != nil { if err := dht.runBootstrap(ctx, cfg); err != nil {
log.Error(err) log.Error(err)
// A bootstrapping error is important to notice but not fatal. // A bootstrapping error is important to notice but not fatal.
// maybe the client should be able to consume these errors,
// though I dont have a clear use case in mind-- what **could**
// the client do if one of the bootstrap calls fails?
//
// This is also related to the core's bootstrap failures.
// superviseConnections should perhaps allow clients to detect
// bootstrapping problems.
//
// Anyway, passing errors could be done with a bootstrapper object.
// this would imply the client should be able to consume a lot of
// other non-fatal dht errors too. providing this functionality
// should be done correctly DHT-wide.
// NB: whatever the design, clients must ensure they drain errors!
// This pattern is common to many things, perhaps long-running services
// should have something like an ErrStream that allows clients to consume
// periodic errors and take action. It should allow the user to also
// ignore all errors with something like an ErrStreamDiscard. We should
// study what other systems do for ideas.
} }
}) })
...@@ -113,7 +85,7 @@ func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (gop ...@@ -113,7 +85,7 @@ func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (gop
} }
// runBootstrap builds up list of peers by requesting random peer IDs // runBootstrap builds up list of peers by requesting random peer IDs
func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error { func (dht *IpfsDHT) runBootstrap(ctx context.Context, cfg BootstrapConfig) error {
bslog := func(msg string) { bslog := func(msg string) {
log.Debugf("DHT %s dhtRunBootstrap %s -- routing table size: %d", dht.self, msg, dht.routingTable.Size()) log.Debugf("DHT %s dhtRunBootstrap %s -- routing table size: %d", dht.self, msg, dht.routingTable.Size())
} }
...@@ -133,7 +105,7 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error { ...@@ -133,7 +105,7 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error {
} }
// bootstrap sequentially, as results will compound // bootstrap sequentially, as results will compound
ctx, cancel := context.WithTimeout(ctx, DefaultBootstrapTimeout) ctx, cancel := context.WithTimeout(ctx, cfg.Timeout)
defer cancel() defer cancel()
runQuery := func(ctx context.Context, id peer.ID) { runQuery := func(ctx context.Context, id peer.ID) {
p, err := dht.FindPeer(ctx, id) p, err := dht.FindPeer(ctx, id)
...@@ -154,9 +126,9 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error { ...@@ -154,9 +126,9 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error {
if sequential { if sequential {
// these should be parallel normally. but can make them sequential for debugging. // these should be parallel normally. but can make them sequential for debugging.
// note that the core/bootstrap context deadline should be extended too for that. // note that the core/bootstrap context deadline should be extended too for that.
for i := 0; i < queries; i++ { for i := 0; i < cfg.Queries; i++ {
id := randomID() id := randomID()
log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, queries, id) log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, cfg.Queries, id)
runQuery(ctx, id) runQuery(ctx, id)
} }
...@@ -166,13 +138,13 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error { ...@@ -166,13 +138,13 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error {
// normally, we should be selecting on ctx.Done() here too, but this gets // normally, we should be selecting on ctx.Done() here too, but this gets
// complicated to do with WaitGroup, and doesnt wait for the children to exit. // complicated to do with WaitGroup, and doesnt wait for the children to exit.
var wg sync.WaitGroup var wg sync.WaitGroup
for i := 0; i < queries; i++ { for i := 0; i < cfg.Queries; i++ {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
id := randomID() id := randomID()
log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, queries, id) log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, cfg.Queries, id)
runQuery(ctx, id) runQuery(ctx, id)
}() }()
} }
......
...@@ -115,8 +115,15 @@ func DirectAddCat(data []byte, conf testutil.LatencyConfig) error { ...@@ -115,8 +115,15 @@ func DirectAddCat(data []byte, conf testutil.LatencyConfig) error {
} }
defer catter.Close() defer catter.Close()
catter.Bootstrap(ctx, []peer.PeerInfo{adder.Peerstore.PeerInfo(adder.Identity)}) bs1 := []peer.PeerInfo{adder.Peerstore.PeerInfo(adder.Identity)}
adder.Bootstrap(ctx, []peer.PeerInfo{catter.Peerstore.PeerInfo(catter.Identity)}) bs2 := []peer.PeerInfo{catter.Peerstore.PeerInfo(catter.Identity)}
if err := catter.Bootstrap(core.BootstrapConfigWithPeers(bs1)); err != nil {
return err
}
if err := adder.Bootstrap(core.BootstrapConfigWithPeers(bs2)); err != nil {
return err
}
keyAdded, err := coreunix.Add(adder, bytes.NewReader(data)) keyAdded, err := coreunix.Add(adder, bytes.NewReader(data))
if err != nil { if err != nil {
......
...@@ -62,9 +62,15 @@ func RunThreeLeggedCat(data []byte, conf testutil.LatencyConfig) error { ...@@ -62,9 +62,15 @@ func RunThreeLeggedCat(data []byte, conf testutil.LatencyConfig) error {
return err return err
} }
defer bootstrap.Close() defer bootstrap.Close()
boostrapInfo := bootstrap.Peerstore.PeerInfo(bootstrap.PeerHost.ID())
adder.Bootstrap(ctx, []peer.PeerInfo{boostrapInfo}) bis := bootstrap.Peerstore.PeerInfo(bootstrap.PeerHost.ID())
catter.Bootstrap(ctx, []peer.PeerInfo{boostrapInfo}) bcfg := core.BootstrapConfigWithPeers([]peer.PeerInfo{bis})
if err := adder.Bootstrap(bcfg); err != nil {
return err
}
if err := catter.Bootstrap(bcfg); err != nil {
return err
}
keyAdded, err := coreunix.Add(adder, bytes.NewReader(data)) keyAdded, err := coreunix.Add(adder, bytes.NewReader(data))
if err != nil { if err != nil {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论