Pull request 2352: AGDNS-2690-signal-handler

Merge in DNS/adguard-home from AGDNS-2690-signal-handler to master

Squashed commit of the following:

commit b6822142312ac814e7c206218bb079a4f364192a
Merge: f597ea1fd 8b2ab8ea8
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 3 18:10:27 2025 +0300

    Merge branch 'master' into AGDNS-2690-signal-handler

commit f597ea1fd27c1153f2b08a339fd17361807627e6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 27 20:45:55 2025 +0300

    all: fix logger

commit 582e315ecac45e49e156bddb5755f5fb7114111c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 27 18:40:57 2025 +0300

    home: imp code

commit 1e0f48d32a654b71a9c0a9e3cf3afceab00bc7e5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 27 15:26:19 2025 +0300

    home: imp docs

commit aa43bbc1b20392429d606de5cfd1b1fed940755a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Feb 24 17:24:33 2025 +0300

    home: signal handler
This commit is contained in:
Stanislav Chzhen
2025-03-03 18:23:01 +03:00
parent 8b2ab8ea87
commit 61fe269cb8
7 changed files with 167 additions and 39 deletions

View File

@@ -75,6 +75,7 @@ func (clients *clientsContainer) Init(
etcHosts *aghnet.HostsContainer,
arpDB arpdb.Interface,
filteringConf *filtering.Config,
sigHdlr *signalHandler,
) (err error) {
// TODO(s.chzhen): Refactor it.
if clients.storage != nil {
@@ -120,6 +121,8 @@ func (clients *clientsContainer) Init(
return fmt.Errorf("init client storage: %w", err)
}
sigHdlr.addClientStorage(clients.storage)
return nil
}

View File

@@ -31,6 +31,7 @@ func newClientsContainer(t *testing.T) (c *clientsContainer) {
nil,
nil,
&filtering.Config{},
newSignalHandler(nil, nil),
)
require.NoError(t, err)

View File

@@ -113,31 +113,23 @@ func Main(clientBuildFS fs.FS) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() {
ctx := context.Background()
for {
sig := <-signals
log.Info("Received signal %q", sig)
switch sig {
case syscall.SIGHUP:
globalContext.clients.storage.ReloadARP(ctx)
globalContext.tls.reload()
default:
cleanup(ctx)
cleanupAlways()
close(done)
}
}
}()
ctx := context.Background()
sigHdlr := newSignalHandler(signals, func(ctx context.Context) {
cleanup(ctx)
cleanupAlways()
close(done)
})
go sigHdlr.handle(ctx)
if opts.serviceControlAction != "" {
handleServiceControlAction(opts, clientBuildFS, signals, done)
handleServiceControlAction(opts, clientBuildFS, signals, done, sigHdlr)
return
}
// run the protection
run(opts, clientBuildFS, done)
run(opts, clientBuildFS, done, sigHdlr)
}
// setupContext initializes [globalContext] fields. It also reads and upgrades
@@ -278,7 +270,11 @@ func setupOpts(opts options) (err error) {
}
// initContextClients initializes Context clients and related fields.
func initContextClients(ctx context.Context, logger *slog.Logger) (err error) {
func initContextClients(
ctx context.Context,
logger *slog.Logger,
sigHdlr *signalHandler,
) (err error) {
err = setupDNSFilteringConf(ctx, logger, config.Filtering)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
@@ -313,6 +309,7 @@ func initContextClients(ctx context.Context, logger *slog.Logger) (err error) {
globalContext.etcHosts,
arpDB,
config.Filtering,
sigHdlr,
)
}
@@ -583,7 +580,7 @@ func fatalOnError(err error) {
// run configures and starts AdGuard Home.
//
// TODO(e.burkov): Make opts a pointer.
func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalHandler) {
// Configure working dir.
err := initWorkingDir(opts)
fatalOnError(err)
@@ -599,6 +596,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
// TODO(a.garipov): Use slog everywhere.
slogLogger := newSlogLogger(ls)
sigHdlr.swapLogger(slogLogger)
// Print the first message after logger is configured.
log.Info(version.Full())
@@ -621,7 +619,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
// TODO(s.chzhen): Use it for the entire initialization process.
ctx := context.Background()
err = initContextClients(ctx, slogLogger)
err = initContextClients(ctx, slogLogger, sigHdlr)
fatalOnError(err)
err = setupOpts(opts)
@@ -664,6 +662,8 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
onConfigModified()
}
sigHdlr.addTLSManager(globalContext.tls)
globalContext.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, customURL)
fatalOnError(err)

View File

@@ -36,6 +36,7 @@ type program struct {
signals chan os.Signal
done chan struct{}
opts options
sigHdlr *signalHandler
}
// type check
@@ -47,7 +48,7 @@ func (p *program) Start(_ service.Service) (err error) {
args := p.opts
args.runningAsService = true
go run(args, p.clientBuildFS, p.done)
go run(args, p.clientBuildFS, p.done, p.sigHdlr)
return nil
}
@@ -204,6 +205,7 @@ func handleServiceControlAction(
clientBuildFS fs.FS,
signals chan os.Signal,
done chan struct{},
sigHdlr *signalHandler,
) {
// Call chooseSystem explicitly to introduce OpenBSD support for service
// package. It's a noop for other GOOS values.
@@ -244,6 +246,7 @@ func handleServiceControlAction(
signals: signals,
done: done,
opts: runOpts,
sigHdlr: sigHdlr,
}, svcConfig)
if err != nil {
log.Fatalf("service: initializing service: %s", err)

121
internal/home/signal.go Normal file
View File

@@ -0,0 +1,121 @@
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()
}
}