mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-04-18 15:21:04 +03:00
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 commitf597ea1fd2
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Feb 27 20:45:55 2025 +0300 all: fix logger commit582e315eca
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Feb 27 18:40:57 2025 +0300 home: imp code commit1e0f48d32a
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Feb 27 15:26:19 2025 +0300 home: imp docs commitaa43bbc1b2
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Feb 24 17:24:33 2025 +0300 home: signal handler
This commit is contained in:
parent
8b2ab8ea87
commit
61fe269cb8
7 changed files with 167 additions and 39 deletions
10
go.mod
10
go.mod
|
@ -4,7 +4,7 @@ go 1.23.6
|
|||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.75.0
|
||||
github.com/AdguardTeam/golibs v0.32.1
|
||||
github.com/AdguardTeam/golibs v0.32.5
|
||||
github.com/AdguardTeam/urlfilter v0.20.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
||||
|
@ -33,9 +33,9 @@ require (
|
|||
github.com/stretchr/testify v1.10.0
|
||||
github.com/ti-mo/netfilter v0.5.2
|
||||
go.etcd.io/bbolt v1.4.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
|
||||
golang.org/x/net v0.35.0
|
||||
golang.org/x/sys v0.30.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -63,6 +63,6 @@ require (
|
|||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
gonum.org/v1/gonum v0.15.1 // indirect
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -1,7 +1,7 @@
|
|||
github.com/AdguardTeam/dnsproxy v0.75.0 h1:v8/Oq/xPYzNoALR7SEUZEIbKmjnPcXLVhJLFVbrozEc=
|
||||
github.com/AdguardTeam/dnsproxy v0.75.0/go.mod h1:O2qoXwF4BUBFui7OMUiWSYwapEDcYxKWeur4+jfy9nM=
|
||||
github.com/AdguardTeam/golibs v0.32.1 h1:Ajf6Q0k+A9zjFbj8HOzNAbHImrV4JtbT0vwy02D6VeI=
|
||||
github.com/AdguardTeam/golibs v0.32.1/go.mod h1:dXRLSsnJQDxOfQVl0ochy1bfk4NgnJQGQdR1YPJdwcw=
|
||||
github.com/AdguardTeam/golibs v0.32.5 h1:4Rkv2xBnyJe6l/EM2MFgoY1S4pweYwDgLTYg2MDArEA=
|
||||
github.com/AdguardTeam/golibs v0.32.5/go.mod h1:agsvz8Iyv0uV9NU56hpCoFLAtSPkiBf9nPVhDvdUIb0=
|
||||
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
||||
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
|
@ -128,10 +128,10 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
|||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
|
@ -142,8 +142,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
|
@ -169,14 +169,14 @@ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
|
||||
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
|
||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ func newClientsContainer(t *testing.T) (c *clientsContainer) {
|
|||
nil,
|
||||
nil,
|
||||
&filtering.Config{},
|
||||
newSignalHandler(nil, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
121
internal/home/signal.go
Normal 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()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue