summaryrefslogtreecommitdiff
path: root/internal/cli/serve.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-26 09:09:19 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-26 09:14:14 +0000
commit3f9843b34d7ae9df2dd9c69427ecab45744b97e9 (patch)
tree1c667c17d77b43a1e5fbcae464068a74c2857fb5 /internal/cli/serve.go
parentdac676e8284725c8ec6de08282fe08a9b519ccc8 (diff)
feat: graceful shutdown — drain workers before exit (default 3m timeout)
- Add workerWg to Pool; Shutdown() closes workCh and waits for all in-flight execute/executeResume goroutines to finish - Signal handler now shuts down HTTP first, then drains the pool - ShutdownTimeout config field (toml: shutdown_timeout); default 3m - Tests: WaitsForWorkers and TimesOut Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/cli/serve.go')
-rw-r--r--internal/cli/serve.go26
1 files changed, 21 insertions, 5 deletions
diff --git a/internal/cli/serve.go b/internal/cli/serve.go
index 644392e..f7493ed 100644
--- a/internal/cli/serve.go
+++ b/internal/cli/serve.go
@@ -174,15 +174,31 @@ func serve(addr string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
+ workerTimeout := 3 * time.Minute
+ if cfg.ShutdownTimeout > 0 {
+ workerTimeout = cfg.ShutdownTimeout
+ }
+
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigCh
- logger.Info("shutting down server...")
- shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 5*time.Second)
- defer shutdownCancel()
- if err := httpSrv.Shutdown(shutdownCtx); err != nil {
- logger.Warn("shutdown error", "err", err)
+ logger.Info("shutting down: draining workers...", "timeout", workerTimeout)
+
+ // Stop the HTTP server so no new requests come in.
+ httpCtx, httpCancel := context.WithTimeout(ctx, 5*time.Second)
+ defer httpCancel()
+ if err := httpSrv.Shutdown(httpCtx); err != nil {
+ logger.Warn("http shutdown error", "err", err)
+ }
+
+ // Wait for in-flight task workers to finish.
+ workerCtx, workerCancel := context.WithTimeout(context.Background(), workerTimeout)
+ defer workerCancel()
+ if err := srv.Pool().Shutdown(workerCtx); err != nil {
+ logger.Warn("worker drain timed out", "err", err)
+ } else {
+ logger.Info("all workers finished cleanly")
}
}()