From ee01eca0699cbf2885783daba9da508fd7bc8c4e Mon Sep 17 00:00:00 2001 From: sudoforge Date: Sat, 14 Mar 2026 18:52:14 -0700 Subject: [PATCH] webui: improve the browser opening sequence (#1537) This change refactors the browser opening sequence, adding a healthcheck (with exponential backoff) that ensures the http server is running before opening the browser. This is done in a simple goroutine to allow the main thread to continue (and actually attempt to start the server). SIGQUIT was added as a supported handler, as this signal is used by applications that they are voluntarily quitting, which we do if the maximum number of attempts has been reached without successfully determining that the http server is up. Additional logs are emitted to provide useful context in the event of an action (either opening the browser or failing to reach the http server). This will help end users identify what is happening under the hood, and determine if there are issues, which will lead to better error reports and cooperative debugging. Refs: #1536 Change-Id: I2e2a379ed20a6b3c6d95a209b915f71d56408e1a --- commands/webui.go | 59 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/commands/webui.go b/commands/webui.go index db69d7d37f3127fe6f502e8b19667efe4f7b4790..620bb412e20b8b489d6f48c045e69c38e59056ac 100644 --- a/commands/webui.go +++ b/commands/webui.go @@ -137,11 +137,11 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error { quit := make(chan os.Signal, 1) // register as handler of the interrupt signal to trigger the teardown - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + signal.Notify(quit, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, os.Interrupt) go func() { <-quit - env.Out.Println("WebUI is shutting down...") + env.Out.Println("shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -163,7 +163,7 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error { env.Out.Printf("Web UI: %s\n", webUiAddr) env.Out.Printf("Graphql API: http://%s/graphql\n", addr) env.Out.Printf("Graphql Playground: http://%s/playground\n", addr) - env.Out.Println("Press Ctrl+c to quit") + env.Out.Printf("[ Press Ctrl+c to quit ]\n\n") configOpen, err := env.Repo.AnyConfig().ReadBool(webUIOpenConfigKey) if errors.Is(err, repository.ErrNoConfigEntry) { @@ -176,10 +176,28 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error { shouldOpen := (configOpen && !opts.noOpen) || opts.open if shouldOpen { - err = open.Run(toOpen) - if err != nil { - env.Out.Println(err) - } + go func() { + maxAttempts := 3 + if isUp(toOpen, maxAttempts, 3*time.Second) { + err = open.Run(toOpen) + if err != nil { + env.Out.Println(err) + return + } + + env.Out.Printf("opened your default browser to url: %s\n", toOpen) + return + } else { + env.Out.Printf( + "uh oh! it appears that the http server hasn't started.\n"+ + "we failed to reach %s after %d attempts, exiting now.\n", + toOpen, + maxAttempts, + ) + quit <- syscall.SIGQUIT + return + } + }() } err = srv.ListenAndServe() @@ -188,7 +206,30 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error { } <-done - - env.Out.Println("WebUI stopped") return nil } + +func isUp(url string, maxRetries int, initialDelay time.Duration) bool { + client := &http.Client{ + Timeout: 5 * time.Second, + } + + delay := initialDelay + + for attempt := 1; attempt <= maxRetries; attempt++ { + resp, err := client.Head(url) + if err == nil { + resp.Body.Close() + if resp.StatusCode >= 200 && resp.StatusCode < 400 { + return true + } + } + + if attempt < maxRetries { + time.Sleep(delay) + delay *= 2 + } + } + + return false +}