From c3c59445ff5272355f4cce1a7171ec5c6672bb8a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 25 Jan 2021 14:53:31 -0800 Subject: [PATCH] ipn/ipnserver: on Windows in unattended mode, wait for Engine forever Updates #1187 --- ipn/ipnserver/server.go | 62 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index cfccc2e74..7518c6e2a 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -537,6 +537,46 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( eng, err := getEngine() if err != nil { logf("ipnserver: initial getEngine call: %v", err) + + // Issue 1187: on Windows, in unattended mode, + // sometimes we try 5 times and fail to create the + // engine before the system's ready. Hack until the + // bug if fixed properly: if we're running in + // unattended mode on Windows, keep trying forever, + // waiting for the machine to be ready (networking to + // come up?) and then dial our own safesocket TCP + // listener to wake up the usual mechanism that lets + // us surface getEngine errors to UI clients. (We + // don't want to just call getEngine in a loop without + // the listener.Accept, as we do want to handle client + // connections so we can tell them about errors) + + bootRaceWaitForEngine, bootRaceWaitForEngineCancel := context.WithTimeout(context.Background(), time.Minute) + if runtime.GOOS == "windows" && opts.AutostartStateKey != "" { + logf("ipnserver: in unattended mode, waiting for engine availability") + getEngine = getEngineUntilItWorksWrapper(getEngine) + // Wait for it to be ready. + go func() { + defer bootRaceWaitForEngineCancel() + t0 := time.Now() + for { + time.Sleep(10 * time.Second) + if _, err := getEngine(); err != nil { + logf("ipnserver: unattended mode engine load: %v", err) + continue + } + c, err := net.Dial("tcp", listen.Addr().String()) + logf("ipnserver: engine created after %v; waking up Accept: Dial error: %v", time.Since(t0).Round(time.Second), err) + if err == nil { + c.Close() + } + break + } + }() + } else { + bootRaceWaitForEngineCancel() + } + for i := 1; ctx.Err() == nil; i++ { c, err := listen.Accept() if err != nil { @@ -544,6 +584,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( bo.BackOff(ctx, err) continue } + <-bootRaceWaitForEngine.Done() logf("ipnserver: try%d: trying getEngine again...", i) eng, err = getEngine() if err == nil { @@ -756,6 +797,27 @@ func FixedEngine(eng wgengine.Engine) func() (wgengine.Engine, error) { return func() (wgengine.Engine, error) { return eng, nil } } +// getEngineUntilItWorksWrapper returns a getEngine wrapper that does +// not call getEngine concurrently and stops calling getEngine once +// it's returned a working engine. +func getEngineUntilItWorksWrapper(getEngine func() (wgengine.Engine, error)) func() (wgengine.Engine, error) { + var mu sync.Mutex + var engGood wgengine.Engine + return func() (wgengine.Engine, error) { + mu.Lock() + defer mu.Unlock() + if engGood != nil { + return engGood, nil + } + e, err := getEngine() + if err != nil { + return nil, err + } + engGood = e + return e, nil + } +} + type dummyAddr string type oneConnListener struct { conn net.Conn