Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bootstrap): take autobootstrap to completion #403

Merged
merged 6 commits into from
Nov 5, 2019

Conversation

aarshkshah1992
Copy link
Contributor

@Stebalien

This takes #402 to completion. Main changes made are:

  1. Bootstrap(ctx) should be synchronous so that errors in bootstrapping flow to the client
  2. A flag to disable auto bootstrapping so that tests which depend on a specific number of peers in the RT/query results do not fail
  3. Replace RoutingTableScanInterval with BucketPeriod.

Stebalien and others added 2 commits November 1, 2019 00:38
1. Auto bootstrap on start.
2. Make `Bootstrap(ctx)` trigger a bootstrap but not _start_ the bootstrapping
   process.
dht.go Outdated
@@ -171,10 +174,10 @@ func makeDHT(ctx context.Context, h host.Host, dstore ds.Batching, protocols []p
// come up with an alternative solution.
// issue is being tracked at https://github.com/libp2p/go-libp2p-kad-dht/issues/387
/*func (dht *IpfsDHT) rtRecovery(proc goprocess.Process) {
writeResp := func(errorChan chan error, err error) {
writeResp := func(errorChan chan error, errChan error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sed?

dht_bootstrap.go Outdated
if err := dht.selfWalk(ctx); err != nil {
return fmt.Errorf("self walk: error: %s", err)
} else {
*latestSelfWalk = time.Now()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well just make this a field in the dht struct.

// Bootstrap tells the DHT to get into a bootstrapped state.
//
// Note: the context is ignored.
func (dht *IpfsDHT) Bootstrap(_ context.Context) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bootstrap is actually supposed to be async (at the moment). We can change this, but we'll need to propagate the change up the interfaces.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed below, this will now be async.

dht_bootstrap.go Outdated
case req := <-dht.triggerBootstrap:
logger.Infof("triggering a bootstrap: RT has %d peers", dht.routingTable.Size())
err := dht.doBootstrap(ctx, true, &lastSelfWalk)
select {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the err channel always has 1 slot, we shouldn't need to do this. We should be able to just run:

req.errChan <- err
close(req.errChan)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Stebalien: Oh ofcourse, but we can't rely on clients always sending a buffered channel.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this internal?

Copy link
Contributor Author

@aarshkshah1992 aarshkshah1992 Nov 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Stebalien

It really is just a precautionary measure. If in the future, a go-routine within Dht ("client") decides to a trigger a bootstrap & passes an unbuffered channel because of whatever reason(programmer oversight for example), the whole bootstrapping loop comes to a halt unless the "client" reads from it. We don't want to rely on that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh, either way. IMO, the idiom of passing respone channels with a size of 1 is common enough that users should always check how a channel is being used before passing an unbuffered response channel.

But either way works.

select {
case dht.triggerBootstrap <- req:
return <-req.errChan
default:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to behave inconsistently. If we're currently bootstrapping, we'll return immediately.

If we're going to block, a new bootstrap call should probably try to "join" an existing one. However, that will add some complexity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hsanjuan what is cluster's specific need:

  1. Do you need a way to tell the DHT to bootstrap?
  2. Do you need to be able to wait for the DHT too finish bootstrapping?

Note: queries should still work even if the DHT isn't bootstrapped, as long as you have some peers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need a peer to get start getting "well-connected" after the first contact to other peer(s) [before that the peer would have had no connections). I don't need to wait until the end of the bootstrap round (it can just happen in background)

You may say "well, call Bootstrap()" then and not before, but things are a bit more tricky (the dht may have already been bootstrapped externally and what not). Being able to trigger a bootstrap round on demand (independent from the recurring configured one) is handy here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need a peer to get start getting "well-connected" after the first contact to other peer(s) [before that the peer would have had no connections).

This should now be automatic.

Being able to trigger a bootstrap round on demand (independent from the recurring configured one) is handy here.

SGTM. I agree bootstrap should actually trigger a bootstrap.


@aarshkshah1992 given this, I wouldn't wait. I'd just trigger the bootstrap and move on.

opts/options.go Outdated
@@ -149,3 +148,11 @@ func BucketSize(bucketSize int) Option {
return nil
}
}

// DisableAutoBootstrap disables auto bootstrap on the dht
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, all this option actually does is skip the initial bootstrap. We'll still auto-bootstrap.

Given that, really, we only care about this in tests (as far as I know), we could:

  1. Have some kind of hidden option that prevents us from starting the bootstrap loop.
  2. Manually call startBootstrap.

(We could also not start bootstrapping till the user calls Bootstrap but one of my goals here was to remove that requirement so the DHT "just works" out of the box)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option does two things actually:

  1. Skip the initial bootstrap
  2. Disables the "bootstrap if RT size is below minimum threshold" feature as that causes some tests to fail as I've explained in the comment here

I agree that we only need to do this for tests for now. But, what's the harm in having a visible option to disable this should someone wish to run the Dht without this side effect ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Yeah, that makes sense (although it's still a bit confusing as it doesn't completely disable automatic bootstrap). But some good documentation should be enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Actually, I think this is still too confusing and too weird of an option to expose in a public API. I can't think of a single (real) use-case for it and I can think of plenty of ways in which it can be used incorrectly.

If we're going to have a public option to disable automatic bootstrapping, it should completely disable automatic bootstrapping. We can probably just have this option modify the bootstrap config to set the bootstrap intervals to 0 (and then special-case 0 to disable the ticker). That way, we'll only bootstrap on request.

@Stebalien
Copy link
Member

Also, thank you!

@@ -34,9 +34,9 @@ func (nn *netNotifiee) Connected(n network.Network, v network.Conn) {
if dht.host.Network().Connectedness(p) == network.Connected {
bootstrap := dht.routingTable.Size() <= minRTBootstrapThreshold
dht.Update(dht.Context(), p)
if bootstrap {
if bootstrap && dht.triggerAutoBootstrap {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some tests rely on a specific number of peers in the RT/ specific number of results in a query after a peer has finished connecting to other peers. However, this code causes the peer to connect to more peers/adds more peers to the RT than it intended to in the test & that causes the test to fail. So, we need to disable this feature for tests.

@aarshkshah1992
Copy link
Contributor Author

@Stebalien Have addressed your comments. Please take a look.

dht_bootstrap.go Outdated
if walkSelf {
if err := dht.selfWalk(ctx); err != nil {
return fmt.Errorf("self walk: error: %s", err)
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unnecessary else.

@aarshkshah1992
Copy link
Contributor Author

@Stebalien This is now good to go. Thanks for the review.

dht.go Outdated
@@ -159,7 +163,7 @@ func makeDHT(ctx context.Context, h host.Host, dstore ds.Batching, protocols []p
routingTable: rt,
protocols: protocols,
bucketSize: bucketSize,
triggerBootstrap: make(chan struct{}),
triggerBootstrap: make(chan *bootstrapReq),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need the bootstrapReq, as far as I can tell.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Stebalien Indeed. Don't know how I missed it. Thanks.

@Stebalien Stebalien merged commit 4fd6498 into libp2p:master Nov 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants