AdGuardHome/internal/home/signal.go
2025-03-17 20:56:05 +03:00

121 lines
2.8 KiB
Go

package home
import (
"context"
"log/slog"
"os"
"sync"
"sync/atomic"
"syscall"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/osutil"
)
// signalHandler processes incoming signals. It reloads configurations of
// stored entities on SIGHUP and performs cleanup on all other signals.
type signalHandler struct {
// logger is used to log the operation of the signal handler. Initially,
// [slog.Default] is used, but it should be swapped later using
// [signalHandler.swapLogger].
logger *atomic.Pointer[slog.Logger]
// mu protects clientStorage and tlsManager.
mu *sync.Mutex
// clientStorage is used to reload information about runtime clients with an
// ARP source.
clientStorage *client.Storage
// tlsManager is used to reload the TLS configuration.
tlsManager *tlsManager
// signals receives incoming signals.
signals <-chan os.Signal
// cleanup is called to perform cleanup on all incoming signals, except
// SIGHUP.
cleanup func(ctx context.Context)
}
// newSignalHandler returns a new properly initialized *signalHandler.
func newSignalHandler(
signals <-chan os.Signal,
cleanup func(ctx context.Context),
) (h *signalHandler) {
h = &signalHandler{
logger: &atomic.Pointer[slog.Logger]{},
mu: &sync.Mutex{},
signals: signals,
cleanup: cleanup,
}
h.logger.Store(slog.Default())
return h
}
// swapLogger replaces the stored logger with the given logger.
func (h *signalHandler) swapLogger(logger *slog.Logger) {
h.logger.Swap(logger)
}
// addClientStorage stores the client storage.
func (h *signalHandler) addClientStorage(s *client.Storage) {
h.mu.Lock()
defer h.mu.Unlock()
h.clientStorage = s
}
// addTLSManager stores the TLS manager.
func (h *signalHandler) addTLSManager(m *tlsManager) {
h.mu.Lock()
defer h.mu.Unlock()
h.tlsManager = m
}
// handle processes incoming signals. It blocks until a signal is received. It
// reloads configurations of stored entities on SIGHUP, or performs cleanup on
// all other signals. It is intended to be used as a goroutine.
func (h *signalHandler) handle(ctx context.Context) {
// NOTE: Avoid using [slogutil.RecoverAndExit] to prevent immediate
// evaluation of the logger.
defer func() {
v := recover()
if v == nil {
return
}
slogutil.PrintRecovered(ctx, h.logger.Load(), v)
os.Exit(osutil.ExitCodeFailure)
}()
for {
sig := <-h.signals
h.logger.Load().InfoContext(ctx, "received signal", "signal", sig)
switch sig {
case syscall.SIGHUP:
h.reloadConfig(ctx)
default:
h.cleanup(ctx)
}
}
}
// reloadConfig refreshes configurations of stored entities.
func (h *signalHandler) reloadConfig(ctx context.Context) {
h.mu.Lock()
defer h.mu.Unlock()
if h.clientStorage != nil {
h.clientStorage.ReloadARP(ctx)
}
if h.tlsManager != nil {
h.tlsManager.reload(ctx)
}
}