cmd/tailscale: handle IPN callbacks from the backend goroutine

Before this change, the notification and configuration callbacks
raced with the backend goroutine.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
pull/2/head
Elias Naur 5 years ago
parent 4388f145cc
commit bfd730f3d7

@ -116,16 +116,14 @@ func main() {
} }
func (a *App) runBackend() error { func (a *App) runBackend() error {
var cfg *router.Config configs := make(chan *router.Config)
var state NetworkState configErrs := make(chan error)
var service jni.Object
var b *backend
b, err := newBackend(a.appDir, a.jvm, a.store, func(s *router.Config) error { b, err := newBackend(a.appDir, a.jvm, a.store, func(s *router.Config) error {
cfg = s if s == nil {
if b == nil || service == 0 || cfg == nil {
return nil return nil
} }
return b.updateTUN(service, cfg) configs <- s
return <-configErrs
}) })
if err != nil { if err != nil {
return err return err
@ -143,62 +141,82 @@ func (a *App) runBackend() error {
} }
} }
var prefs struct { var prefs struct {
once sync.Once
mu sync.Mutex mu sync.Mutex
prefs *ipn.Prefs prefs *ipn.Prefs
} }
err = b.Start(func(n ipn.Notify) { notifications := make(chan ipn.Notify, 1)
if p := n.Prefs; p != nil { startErr := make(chan error)
prefs.mu.Lock() // Start from a goroutine to avoid deadlock when Start
prefs.prefs = p.Clone() // calls the callback.
prefs.mu.Unlock() go func() {
a.setPrefs(prefs.prefs) startErr <- b.Start(func(n ipn.Notify) {
} notifications <- n
if s := n.State; s != nil { })
oldState := state.State }()
state.State = *s var cfg *router.Config
if service != 0 { var state NetworkState
a.updateNotification(service, state.State) var service jni.Object
for {
select {
case err := <-startErr:
if err != nil {
return err
}
case s := <-configs:
cfg = s
if b == nil || service == 0 || cfg == nil {
configErrs <- nil
break
}
configErrs <- b.updateTUN(service, cfg)
case n := <-notifications:
if p := n.Prefs; p != nil {
prefs.mu.Lock()
prefs.prefs = p.Clone()
prefs.mu.Unlock()
a.setPrefs(prefs.prefs)
prefs.once.Do(func() {
prefs.mu.Lock()
prefs.prefs.Hostname = a.hostname()
p := prefs.prefs
prefs.mu.Unlock()
b.backend.SetPrefs(p)
})
} }
if service != 0 { if s := n.State; s != nil {
if cfg != nil && state.State >= ipn.Starting { oldState := state.State
if err := b.updateTUN(service, cfg); err != nil { state.State = *s
a.notifyVPNClosed() if service != 0 {
a.updateNotification(service, state.State)
}
if service != 0 {
if cfg != nil && state.State >= ipn.Starting {
if err := b.updateTUN(service, cfg); err != nil {
a.notifyVPNClosed()
}
} else {
b.CloseTUNs()
} }
} else {
b.CloseTUNs()
} }
} // Stop VPN if we logged out.
// Stop VPN if we logged out. if oldState > ipn.Stopped && state.State <= ipn.Stopped {
if oldState > ipn.Stopped && state.State <= ipn.Stopped { if err := a.callVoidMethod(a.appCtx, "stopVPN", "()V"); err != nil {
if err := a.callVoidMethod(a.appCtx, "stopVPN", "()V"); err != nil { fatalErr(err)
fatalErr(err) }
} }
a.notify(state)
} }
a.notify(state) if u := n.BrowseToURL; u != nil {
} a.setURL(*u)
if u := n.BrowseToURL; u != nil { }
a.setURL(*u) if m := n.NetMap; m != nil {
} state.NetworkMap = m
if m := n.NetMap; m != nil { a.notify(state)
state.NetworkMap = m if service != 0 {
a.notify(state) alarm(a.notifyExpiry(service, m.Expiry))
if service != 0 { }
alarm(a.notifyExpiry(service, m.Expiry))
} }
}
})
if err != nil {
return err
}
{
prefs.mu.Lock()
prefs.prefs.Hostname = a.hostname()
p := prefs.prefs
prefs.mu.Unlock()
b.backend.SetPrefs(p)
}
for {
select {
case <-alarmChan: case <-alarmChan:
if m := state.NetworkMap; m != nil && service != 0 { if m := state.NetworkMap; m != nil && service != 0 {
alarm(a.notifyExpiry(service, m.Expiry)) alarm(a.notifyExpiry(service, m.Expiry))

Loading…
Cancel
Save