From 032470a5d3ef04dd866f15a5f493b13e42a88e84 Mon Sep 17 00:00:00 2001 From: Igor Serganov Date: Tue, 28 Jul 2026 10:15:02 -0700 Subject: [PATCH] hscontrol: cancel tailsql on graceful shutdown Serve stored a Background context for tailsql and called context.Done() during signal shutdown. Done only returns the done channel and does not cancel, so tailsql never stopped when Headscale shut down. Create a cancellable child of the Serve context, run `runTailSQLService` in the listener errgroup so its error is surfaced, and call the cancel func on shutdown - tailsql unblocks on ctx.Done. (cherry picked from commit a9f80d88023a6593e0f814ac32928e74317ac111) --- hscontrol/app.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/hscontrol/app.go b/hscontrol/app.go index d2c9eee8..417bd4e8 100644 --- a/hscontrol/app.go +++ b/hscontrol/app.go @@ -876,7 +876,7 @@ func (h *Headscale) Serve() error { log.Info().Msg("metrics server disabled (metrics_listen_addr is empty)") } - var tailsqlContext context.Context + var tailsqlCancel context.CancelFunc if tailsqlEnabled { if h.cfg.Database.Type != types.DatabaseSqlite { @@ -891,9 +891,12 @@ func (h *Headscale) Serve() error { log.Fatal().Msg("tailsql requires TS_AUTHKEY to be set") } - tailsqlContext = context.Background() + var tailsqlCtx context.Context + tailsqlCtx, tailsqlCancel = context.WithCancel(ctx) - go runTailSQLService(ctx, util.TSLogfWrapper(), tailsqlStateDir, h.cfg.Database.Sqlite.Path) //nolint:errcheck + errorGroup.Go(func() error { + return runTailSQLService(tailsqlCtx, util.TSLogfWrapper(), tailsqlStateDir, h.cfg.Database.Sqlite.Path) + }) } // Handle common process-killing signals so we can gracefully shut down: @@ -975,9 +978,9 @@ func (h *Headscale) Serve() error { grpcListener.Close() } - if tailsqlContext != nil { + if tailsqlCancel != nil { info("shutting down tailsql") - tailsqlContext.Done() + tailsqlCancel() } // Close network listeners