Implement proper signal handler
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
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.
|
||||
// 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 {
|
||||
|
||||
Reference in New Issue
Block a user