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#557reviewable/pr827/r1
parent
1819f6f8c8
commit
1fd9958e9d
40
ipn/local.go
40
ipn/local.go
|
@ -56,7 +56,8 @@ type LocalBackend struct {
|
||||||
store StateStore
|
store StateStore
|
||||||
backendLogID string
|
backendLogID string
|
||||||
portpoll *portlist.Poller // may be nil
|
portpoll *portlist.Poller // may be nil
|
||||||
portpollOnce sync.Once
|
portpollOnce sync.Once // guards starting readPoller
|
||||||
|
gotPortPollRes chan struct{} // closed upon first readPoller result
|
||||||
serverURL string // tailcontrol URL
|
serverURL string // tailcontrol URL
|
||||||
newDecompressor func() (controlclient.Decompressor, error)
|
newDecompressor func() (controlclient.Decompressor, error)
|
||||||
|
|
||||||
|
@ -114,6 +115,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengin
|
||||||
backendLogID: logid,
|
backendLogID: logid,
|
||||||
state: NoState,
|
state: NoState,
|
||||||
portpoll: portpoll,
|
portpoll: portpoll,
|
||||||
|
gotPortPollRes: make(chan struct{}),
|
||||||
}
|
}
|
||||||
e.SetLinkChangeCallback(b.linkChange)
|
e.SetLinkChangeCallback(b.linkChange)
|
||||||
b.statusChanged = sync.NewCond(&b.statusLock)
|
b.statusChanged = sync.NewCond(&b.statusLock)
|
||||||
|
@ -407,6 +409,27 @@ func (b *LocalBackend) Start(opts Options) error {
|
||||||
|
|
||||||
b.updateFilter(nil, nil)
|
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
|
var discoPublic tailcfg.DiscoKey
|
||||||
if controlclient.Debug.Disco {
|
if controlclient.Debug.Disco {
|
||||||
discoPublic = b.e.DiscoPublicKey()
|
discoPublic = b.e.DiscoPublicKey()
|
||||||
|
@ -433,15 +456,6 @@ func (b *LocalBackend) Start(opts Options) error {
|
||||||
return err
|
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.mu.Lock()
|
||||||
b.c = cli
|
b.c = cli
|
||||||
endpoints := b.endpoints
|
endpoints := b.endpoints
|
||||||
|
@ -590,6 +604,7 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
|
||||||
// readPoller is a goroutine that receives service lists from
|
// readPoller is a goroutine that receives service lists from
|
||||||
// b.portpoll and propagates them into the controlclient's HostInfo.
|
// b.portpoll and propagates them into the controlclient's HostInfo.
|
||||||
func (b *LocalBackend) readPoller() {
|
func (b *LocalBackend) readPoller() {
|
||||||
|
n := 0
|
||||||
for {
|
for {
|
||||||
ports, ok := <-b.portpoll.C
|
ports, ok := <-b.portpoll.C
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -616,6 +631,11 @@ func (b *LocalBackend) readPoller() {
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
|
||||||
b.doSetHostinfoFilterServices(hi)
|
b.doSetHostinfoFilterServices(hi)
|
||||||
|
|
||||||
|
n++
|
||||||
|
if n == 1 {
|
||||||
|
close(b.gotPortPollRes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue