diff options
| author | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-06-12 08:26:25 -0700 |
|---|---|---|
| committer | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-06-12 08:26:25 -0700 |
| commit | b2c462a3a1353f0653a5964b3a6924538ce83523 (patch) | |
| tree | 90968ec234bbfa44e58fcf887efd9ba15dc280dd /cmd/mobius-hotline-server | |
| parent | 7ebc802d0a269218f05b3b51eb10ac66eacb4d1f (diff) | |
Shut down gracefully instead of exiting from library code
ListenAndServe previously started each listener in a goroutine that
called log.Fatal on any error, which skipped deferred cleanup and made
errors unobservable to callers, and Server.Shutdown terminated the
process with os.Exit. Context cancellation was also ineffective:
Serve only checked ctx between Accept calls, which block indefinitely.
ListenAndServe now binds its listeners up front and returns bind
errors, closes every listener when the context is canceled so accept
loops unblock and return, and reports the first serve loop error to
the caller. Shutdown closes a lazily-initialized channel that cancels
ListenAndServe's context, so the shutdown API works race-free even
though it starts before ListenAndServe. "Server shutting down" is
logged once by ListenAndServe rather than per accept loop, which
produced duplicate or missing lines depending on scheduling.
main.go now treats context.Canceled as a clean exit, logs other server
errors and exits nonzero, and runs deferred cleanup (e.g. Bonjour
shutdown) on the way out.
Diffstat (limited to 'cmd/mobius-hotline-server')
| -rw-r--r-- | cmd/mobius-hotline-server/main.go | 14 |
1 files changed, 10 insertions, 4 deletions
diff --git a/cmd/mobius-hotline-server/main.go b/cmd/mobius-hotline-server/main.go index dced2c5..1c298a2 100644 --- a/cmd/mobius-hotline-server/main.go +++ b/cmd/mobius-hotline-server/main.go @@ -4,10 +4,10 @@ import ( "context" "crypto/tls" "embed" + "errors" "flag" "fmt" "io" - "log" "os" "os/signal" "path" @@ -207,9 +207,9 @@ func main() { reloadFunc() default: + // Canceling the context stops ListenAndServe, which unblocks main for a clean exit. signal.Stop(sigChan) cancel() - os.Exit(0) } } @@ -235,8 +235,14 @@ func main() { defer s.Shutdown() } - // Serve Hotline requests until program exit - log.Fatal(srv.ListenAndServe(ctx)) + // Serve Hotline requests until shutdown is requested via signal, the shutdown API, or a + // server error. + if err := srv.ListenAndServe(ctx); err != nil && !errors.Is(err, context.Canceled) { + slogger.Error("Server error", "err", err) + os.Exit(1) + } + + slogger.Info("Server shut down") } // findConfigPath searches for an existing config directory from the predefined search order. |