From 7b064535bf00fec987fd8580890879f694e0716b Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Sat, 14 Nov 2015 20:56:34 -0700 Subject: [PATCH] Changed SIGINT and added support for HUP, QUIT, and TERM --- caddy/caddy.go | 5 ++- caddy/sigtrap.go | 36 +++++++++++++++----- caddy/sigtrap_posix.go | 74 +++++++++++++++++++++++++++--------------- 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/caddy/caddy.go b/caddy/caddy.go index e6569f42e..b82a58a98 100644 --- a/caddy/caddy.go +++ b/caddy/caddy.go @@ -12,8 +12,7 @@ // You should use caddy.Wait() to wait for all Caddy servers // to quit before your process exits. // -// Importing this package has the side-effect of trapping -// SIGINT on all platforms and SIGUSR1 on not-Windows systems. +// Importing this package has the side-effect of trapping signals. // It has to do this in order to perform shutdowns or reloads. package caddy @@ -275,7 +274,7 @@ func startServers(groupings bindingGroup) error { // Stop stops all servers. It blocks until they are all stopped. // It does NOT execute shutdown callbacks that may have been -// configured by middleware (they are executed on SIGINT). +// configured by middleware (they must be executed separately). func Stop() error { letsencrypt.Deactivate() diff --git a/caddy/sigtrap.go b/caddy/sigtrap.go index 78d2eecc5..f18e6ba70 100644 --- a/caddy/sigtrap.go +++ b/caddy/sigtrap.go @@ -4,30 +4,50 @@ import ( "log" "os" "os/signal" + "sync" "github.com/mholt/caddy/server" ) func init() { - // Trap quit signals (cross-platform) + // Trap interrupt signal (cross-platform); triggers forceful shutdown + // that executes shutdown callbacks first. A second interrupt signal + // will exit the process immediately. go func() { shutdown := make(chan os.Signal, 1) - signal.Notify(shutdown, os.Interrupt, os.Kill) - <-shutdown + signal.Notify(shutdown, os.Interrupt) - var exitCode int + for i := 0; true; i++ { + <-shutdown + if i > 0 { + log.Println("[INFO] SIGINT: Force quit") + os.Exit(1) + } + + log.Println("[INFO] SIGINT: Shutting down") + go os.Exit(executeShutdownCallbacks("SIGINT")) + } + }() +} + +// executeShutdownCallbacks executes the shutdown callbacks as initiated +// by signame. It logs any errors and returns the recommended exit status. +// This function is idempotent; subsequent invocations always return 0. +func executeShutdownCallbacks(signame string) (exitCode int) { + shutdownCallbacksOnce.Do(func() { serversMu.Lock() errs := server.ShutdownCallbacks(servers) serversMu.Unlock() if len(errs) > 0 { for _, err := range errs { - log.Printf("[ERROR] Shutting down: %v", err) + log.Printf("[ERROR] %s shutdown: %v", signame, err) } exitCode = 1 } - - os.Exit(exitCode) - }() + }) + return } + +var shutdownCallbacksOnce sync.Once diff --git a/caddy/sigtrap_posix.go b/caddy/sigtrap_posix.go index 2d9898f42..1d602a910 100644 --- a/caddy/sigtrap_posix.go +++ b/caddy/sigtrap_posix.go @@ -11,40 +11,62 @@ import ( ) func init() { - // Trap POSIX-only signals + // Trap all POSIX-only signals go func() { - reload := make(chan os.Signal, 1) - signal.Notify(reload, syscall.SIGUSR1) // reload configuration + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1) - for { - <-reload + for sig := range sigchan { + switch sig { + case syscall.SIGTERM: + log.Println("[INFO] SIGTERM: Terminating process") + os.Exit(0) - log.Println("[INFO] SIGUSR1: Reloading") + case syscall.SIGQUIT: + log.Println("[INFO] SIGQUIT: Shutting down") + exitCode := executeShutdownCallbacks("SIGQUIT") + err := Stop() + if err != nil { + log.Printf("[ERROR] SIGQUIT stop: %v", err) + exitCode = 1 + } + os.Exit(exitCode) - var updatedCaddyfile Input + case syscall.SIGHUP: + log.Println("[INFO] SIGHUP: Hanging up") + err := Stop() + if err != nil { + log.Printf("[ERROR] SIGHUP stop: %v", err) + } - caddyfileMu.Lock() - if caddyfile == nil { - // Hmm, did spawing process forget to close stdin? Anyhow, this is unusual. - log.Println("[ERROR] SIGUSR1: no Caddyfile to reload (was stdin left open?)") - caddyfileMu.Unlock() - continue - } - if caddyfile.IsFile() { - body, err := ioutil.ReadFile(caddyfile.Path()) - if err == nil { - updatedCaddyfile = CaddyfileInput{ - Filepath: caddyfile.Path(), - Contents: body, - RealFile: true, + case syscall.SIGUSR1: + log.Println("[INFO] SIGUSR1: Reloading") + + var updatedCaddyfile Input + + caddyfileMu.Lock() + if caddyfile == nil { + // Hmm, did spawing process forget to close stdin? Anyhow, this is unusual. + log.Println("[ERROR] SIGUSR1: no Caddyfile to reload (was stdin left open?)") + caddyfileMu.Unlock() + continue + } + if caddyfile.IsFile() { + body, err := ioutil.ReadFile(caddyfile.Path()) + if err == nil { + updatedCaddyfile = CaddyfileInput{ + Filepath: caddyfile.Path(), + Contents: body, + RealFile: true, + } } } - } - caddyfileMu.Unlock() + caddyfileMu.Unlock() - err := Restart(updatedCaddyfile) - if err != nil { - log.Printf("[ERROR] SIGUSR1: %v", err) + err := Restart(updatedCaddyfile) + if err != nil { + log.Printf("[ERROR] SIGUSR1: %v", err) + } } } }()