Implement proper signal handler

This commit is contained in:
eleksir
2026-04-04 15:50:09 +03:00
parent 5e7dd4b960
commit 2201fc72f1
4 changed files with 52 additions and 8 deletions

View File

@@ -135,6 +135,15 @@ This includes:
- Protocol errors
- TLS-specific connection issues
### Graceful Shutdown
When backer receives a termination signal (SIGTERM or SIGINT), it will:
1. Log a warning message: "Shutdown signal received, shutting down immediately..."
2. Stop accepting new connections and abort any in-progress downloads immediately
3. Clean up resources and exit
This ensures no partial/corrupted backups are left. Clients will need to restart their backup. For a forced immediate stop, use `SIGKILL`.
## Special Thanks
This project was developed with the assistance of **opencode** (AI co-programmer powered by MIMO). The AI helped with:

View File

@@ -41,6 +41,10 @@ func main() {
log.Fatalf("Failed to create server: %v", err)
}
// Setup signal handling for immediate shutdown.
log.Infof("Installing signal handler")
setupSignalHandler(server)
log.Infof("Starting backer version %s at %s", version, server.Addr)
if backer.C.NoHTTPS {
@@ -60,6 +64,8 @@ func main() {
log.Fatalf("Failed to start server: %v", err)
}
}
log.Infof("Server stopped")
}
// defaultConfigPath returns the default config file path based on the OS.

29
cmd/backer/sigHandler.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"os"
"os/signal"
"syscall"
"backer/internal/backer"
"backer/internal/log"
)
// setupSignalHandler sets up signal handling for immediate shutdown.
// SIGTERM is commonly sent by systemd, k8s, and other container orchestrators.
// SIGINT is sent on Ctrl+C.
func setupSignalHandler(server *backer.ServerWrapper) {
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
go func() {
<-signalCh
log.Warnf("Shutdown signal received, shutting down immediately...")
// Close the embedded http.Server directly.
if closeErr := server.Close(); closeErr != nil {
log.Errorf("Error during server shutdown: %v", closeErr)
}
}()
}
/* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -54,8 +54,8 @@ func withServerHeader(next http.Handler) http.Handler {
}
// NewServer starts new server instance with given config file.
// It returns a serverWrapper that intercepts and classifies server errors.
func NewServer(configPath string) (*serverWrapper, error) { //nolint: revive
// It returns a ServerWrapper that intercepts and classifies server errors.
func NewServer(configPath string) (*ServerWrapper, error) {
_ = log.Init("info", "") // Initialize logger with defaults for early logging.
if err := LoadConfig(configPath); err != nil {
@@ -175,7 +175,7 @@ func NewServer(configPath string) (*serverWrapper, error) { //nolint: revive
// HTTP mode if nohttps is enabled in config.
// HTTPS mode if nohttps is disabled in config.
if C.NoHTTPS {
return &serverWrapper{
return &ServerWrapper{
Server: &http.Server{
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
Handler: withServerHeader(mux),
@@ -186,7 +186,7 @@ func NewServer(configPath string) (*serverWrapper, error) { //nolint: revive
}, nil
}
return &serverWrapper{
return &ServerWrapper{
Server: &http.Server{
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
Handler: withServerHeader(mux),
@@ -284,15 +284,15 @@ func getCompressionAlgorithm(requestedExt string) string {
}
type (
// serverWrapper wraps [http.Server] to intercept and classify errors.
serverWrapper struct {
// ServerWrapper wraps [http.Server] to intercept and classify errors.
ServerWrapper struct {
*http.Server
}
)
// Serve starts the server and classifies any errors returned.
// TLS-related errors are logged at debug level, other errors at warn level.
func (sw *serverWrapper) Serve() error {
func (sw *ServerWrapper) Serve() error {
err := sw.ListenAndServe()
if err != nil {
@@ -307,7 +307,7 @@ func (sw *serverWrapper) Serve() error {
}
// ServeTLS starts the TLS server and classifies any errors returned.
func (sw *serverWrapper) ServeTLS(certFile, keyFile string) error {
func (sw *ServerWrapper) ServeTLS(certFile, keyFile string) error {
err := sw.ListenAndServeTLS(certFile, keyFile)
if err != nil {