ipn: wait for initial portpoll result before starting controlclient

We were creating the controlclient and starting the portpoll concurrently,
which frequently resulted in the first controlclient connection being canceled
by the firsdt portpoll result ~milliseconds later, resulting in another
HTTP request.

Instead, wait a bit for the first portpoll result so it's much less likely to
interrupt our controlclient connection.

Updates tailscale/corp#557
pull/827/head
Brad Fitzpatrick 4 years ago
parent 1819f6f8c8
commit 1fd9958e9d

@ -56,8 +56,9 @@ type LocalBackend struct {
store StateStore
backendLogID string
portpoll *portlist.Poller // may be nil
portpollOnce sync.Once
serverURL string // tailcontrol URL
portpollOnce sync.Once // guards starting readPoller
gotPortPollRes chan struct{} // closed upon first readPoller result
serverURL string // tailcontrol URL
newDecompressor func() (controlclient.Decompressor, error)
filterHash string
@ -105,15 +106,16 @@ func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengin
}
b := &LocalBackend{
ctx: ctx,
ctxCancel: cancel,
logf: logf,
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
e: e,
store: store,
backendLogID: logid,
state: NoState,
portpoll: portpoll,
ctx: ctx,
ctxCancel: cancel,
logf: logf,
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
e: e,
store: store,
backendLogID: logid,
state: NoState,
portpoll: portpoll,
gotPortPollRes: make(chan struct{}),
}
e.SetLinkChangeCallback(b.linkChange)
b.statusChanged = sync.NewCond(&b.statusLock)
@ -407,6 +409,27 @@ func (b *LocalBackend) Start(opts Options) error {
b.updateFilter(nil, nil)
if b.portpoll != nil {
b.portpollOnce.Do(func() {
go b.portpoll.Run(b.ctx)
go b.readPoller()
// Give the poller a second to get results to
// prevent it from restarting our map poll
// HTTP request (via doSetHostinfoFilterServices >
// cli.SetHostinfo). In practice this is very quick.
t0 := time.Now()
timer := time.NewTimer(time.Second)
select {
case <-b.gotPortPollRes:
b.logf("got initial portlist info in %v", time.Since(t0).Round(time.Millisecond))
timer.Stop()
case <-timer.C:
b.logf("timeout waiting for initial portlist")
}
})
}
var discoPublic tailcfg.DiscoKey
if controlclient.Debug.Disco {
discoPublic = b.e.DiscoPublicKey()
@ -433,15 +456,6 @@ func (b *LocalBackend) Start(opts Options) error {
return err
}
// At this point, we have finished using hostinfo without synchronization,
// so it is safe to start readPoller which concurrently writes to it.
if b.portpoll != nil {
b.portpollOnce.Do(func() {
go b.portpoll.Run(b.ctx)
go b.readPoller()
})
}
b.mu.Lock()
b.c = cli
endpoints := b.endpoints
@ -590,6 +604,7 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
// readPoller is a goroutine that receives service lists from
// b.portpoll and propagates them into the controlclient's HostInfo.
func (b *LocalBackend) readPoller() {
n := 0
for {
ports, ok := <-b.portpoll.C
if !ok {
@ -616,6 +631,11 @@ func (b *LocalBackend) readPoller() {
b.mu.Unlock()
b.doSetHostinfoFilterServices(hi)
n++
if n == 1 {
close(b.gotPortPollRes)
}
}
}

Loading…
Cancel
Save