Implement proper signal handler
This commit is contained in:
@@ -135,6 +135,15 @@ This includes:
|
|||||||
- Protocol errors
|
- Protocol errors
|
||||||
- TLS-specific connection issues
|
- 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
|
## Special Thanks
|
||||||
|
|
||||||
This project was developed with the assistance of **opencode** (AI co-programmer powered by MIMO). The AI helped with:
|
This project was developed with the assistance of **opencode** (AI co-programmer powered by MIMO). The AI helped with:
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ func main() {
|
|||||||
log.Fatalf("Failed to create server: %v", err)
|
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)
|
log.Infof("Starting backer version %s at %s", version, server.Addr)
|
||||||
|
|
||||||
if backer.C.NoHTTPS {
|
if backer.C.NoHTTPS {
|
||||||
@@ -60,6 +64,8 @@ func main() {
|
|||||||
log.Fatalf("Failed to start server: %v", err)
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("Server stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultConfigPath returns the default config file path based on the OS.
|
// defaultConfigPath returns the default config file path based on the OS.
|
||||||
|
|||||||
29
cmd/backer/sigHandler.go
Normal file
29
cmd/backer/sigHandler.go
Normal 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: */
|
||||||
@@ -54,8 +54,8 @@ func withServerHeader(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewServer starts new server instance with given config file.
|
// NewServer starts new server instance with given config file.
|
||||||
// It returns a serverWrapper that intercepts and classifies server errors.
|
// It returns a ServerWrapper that intercepts and classifies server errors.
|
||||||
func NewServer(configPath string) (*serverWrapper, error) { //nolint: revive
|
func NewServer(configPath string) (*ServerWrapper, error) {
|
||||||
_ = log.Init("info", "") // Initialize logger with defaults for early logging.
|
_ = log.Init("info", "") // Initialize logger with defaults for early logging.
|
||||||
|
|
||||||
if err := LoadConfig(configPath); err != nil {
|
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.
|
// HTTP mode if nohttps is enabled in config.
|
||||||
// HTTPS mode if nohttps is disabled in config.
|
// HTTPS mode if nohttps is disabled in config.
|
||||||
if C.NoHTTPS {
|
if C.NoHTTPS {
|
||||||
return &serverWrapper{
|
return &ServerWrapper{
|
||||||
Server: &http.Server{
|
Server: &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
|
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
|
||||||
Handler: withServerHeader(mux),
|
Handler: withServerHeader(mux),
|
||||||
@@ -186,7 +186,7 @@ func NewServer(configPath string) (*serverWrapper, error) { //nolint: revive
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &serverWrapper{
|
return &ServerWrapper{
|
||||||
Server: &http.Server{
|
Server: &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
|
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
|
||||||
Handler: withServerHeader(mux),
|
Handler: withServerHeader(mux),
|
||||||
@@ -284,15 +284,15 @@ func getCompressionAlgorithm(requestedExt string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// serverWrapper wraps [http.Server] to intercept and classify errors.
|
// ServerWrapper wraps [http.Server] to intercept and classify errors.
|
||||||
serverWrapper struct {
|
ServerWrapper struct {
|
||||||
*http.Server
|
*http.Server
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Serve starts the server and classifies any errors returned.
|
// Serve starts the server and classifies any errors returned.
|
||||||
// TLS-related errors are logged at debug level, other errors at warn level.
|
// 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()
|
err := sw.ListenAndServe()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -307,7 +307,7 @@ func (sw *serverWrapper) Serve() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServeTLS starts the TLS server and classifies any errors returned.
|
// 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)
|
err := sw.ListenAndServeTLS(certFile, keyFile)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user