Skip to content

Commit d712d8c

Browse files
committed
netresource: Improve logging, fix bug with listeners being recreated on reload
1 parent f82742b commit d712d8c

File tree

10 files changed

+74
-26
lines changed

10 files changed

+74
-26
lines changed

dist/systemd/maddy.service

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ RestartPreventExitStatus=2
7575

7676
ExecStart=/usr/local/bin/maddy run
7777

78-
ExecReload=/bin/kill -USR1 $MAINPID
7978
ExecReload=/bin/kill -USR2 $MAINPID
8079

8180
[Install]

dist/systemd/[email protected]

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ RestartPreventExitStatus=2
7171

7272
ExecStart=/usr/local/bin/maddy --config /etc/maddy/%i.conf run
7373

74-
ExecReload=/bin/kill -USR1 $MAINPID
7574
ExecReload=/bin/kill -USR2 $MAINPID
7675

7776
[Install]

docs/reference/endpoints/smtp.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ smtp tcp://0.0.0.0:25 {
1414
sasl_login no
1515
read_timeout 10m
1616
write_timeout 1m
17+
shutdown_timeout 3m
1718
max_message_size 32M
1819
max_header_size 1M
1920
auth pam
@@ -125,6 +126,14 @@ I/O write timeout.
125126

126127
---
127128

129+
### shutdown_timeout _duration_
130+
Default: `3m`
131+
132+
Time to wait until forcibly closing connections on server shutdown
133+
or configuration reload.
134+
135+
---
136+
128137
### max_message_size _size_
129138
Default: `32M`
130139

framework/module/lifetime.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ func (lt *LifetimeTracker) ReloadAll() error {
9696

9797
// StopAll calls Stop for all registered LifetimeModule instances.
9898
func (lt *LifetimeTracker) StopAll() error {
99-
for _, entry := range lt.instances {
99+
for i := len(lt.instances) - 1; i >= 0; i-- {
100+
entry := lt.instances[i]
101+
100102
if !entry.started {
101103
continue
102104
}

framework/resource/netresource/listen.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ var (
1313
)
1414

1515
func CloseUnusedListeners() error {
16-
return tracker.Close()
16+
return tracker.CloseUnused()
17+
}
18+
19+
func CloseAllListeners() {
20+
tracker.Close()
1721
}
1822

1923
func ResetListenersUsage() {

framework/resource/netresource/tracker.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,8 @@ func (lt *ListenerTracker) ResetUsage() {
6767
}
6868

6969
func (lt *ListenerTracker) CloseUnused() error {
70-
lt.tcp.CloseUnused(func(key string) bool {
71-
return false
72-
})
73-
lt.unix.CloseUnused(func(key string) bool {
74-
return false
75-
})
70+
lt.tcp.CloseUnused(func(key string) bool { return true })
71+
lt.unix.CloseUnused(func(key string) bool { return true })
7672
return nil
7773
}
7874

@@ -83,9 +79,11 @@ func (lt *ListenerTracker) Close() error {
8379
}
8480

8581
func NewListenerTracker(log *log.Logger) *ListenerTracker {
86-
return &ListenerTracker{
82+
lt := &ListenerTracker{
8783
logger: log,
88-
tcp: resource.NewTracker[*net.TCPListener](resource.NewSingleton[*net.TCPListener]()),
89-
unix: resource.NewTracker[*net.UnixListener](resource.NewSingleton[*net.UnixListener]()),
84+
tcp: resource.NewTracker[*net.TCPListener](resource.NewSingleton[*net.TCPListener](log.Sublogger("tcp"))),
85+
unix: resource.NewTracker[*net.UnixListener](resource.NewSingleton[*net.UnixListener](log.Sublogger("unix"))),
9086
}
87+
88+
return lt
9189
}

framework/resource/singleton.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ package resource
22

33
import (
44
"sync"
5+
6+
"github.com/foxcpp/maddy/framework/log"
57
)
68

79
// Singleton represents a set of resources identified by an unique key.
810
type Singleton[T Resource] struct {
11+
log *log.Logger
912
lock sync.RWMutex
1013
resources map[string]T
1114
}
1215

13-
func NewSingleton[T Resource]() *Singleton[T] {
16+
func NewSingleton[T Resource](log *log.Logger) *Singleton[T] {
1417
return &Singleton[T]{
18+
log: log,
1519
resources: make(map[string]T),
1620
}
1721
}
@@ -22,6 +26,7 @@ func (s *Singleton[T]) GetOpen(key string, open func() (T, error)) (T, error) {
2226

2327
existing, ok := s.resources[key]
2428
if ok {
29+
s.log.DebugMsg("resource reused", "key", key)
2530
return existing, nil
2631
}
2732

@@ -31,6 +36,7 @@ func (s *Singleton[T]) GetOpen(key string, open func() (T, error)) (T, error) {
3136
return empty, err
3237
}
3338

39+
s.log.DebugMsg("new resource", "key", key)
3440
s.resources[key] = res
3541

3642
return res, nil
@@ -44,6 +50,7 @@ func (s *Singleton[T]) CloseUnused(isUsed func(key string) bool) error {
4450
if isUsed(key) {
4551
continue
4652
}
53+
s.log.DebugMsg("resource released", "key", key)
4754
res.Close()
4855
delete(s.resources, key)
4956
}
@@ -56,6 +63,7 @@ func (s *Singleton[T]) Close() error {
5663
defer s.lock.Unlock()
5764

5865
for key, res := range s.resources {
66+
s.log.DebugMsg("resource released", "key", key)
5967
res.Close()
6068
delete(s.resources, key)
6169
}

framework/resource/tracker.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func (t *Tracker[T]) CloseUnused(isUsed func(key string) bool) error {
4242

4343
return t.C.CloseUnused(func(key string) bool {
4444
used := t.used[key]
45-
return used && isUsed(key)
45+
used = used && isUsed(key)
46+
if !used {
47+
delete(t.used, key)
48+
}
49+
return used
4650
})
4751
}

internal/endpoint/smtp/smtp.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ type Endpoint struct {
7272
maxReceived int
7373
maxHeaderBytes int64
7474

75-
sessionCnt atomic.Int32
75+
sessionCnt atomic.Int32
76+
shutdownTimeout time.Duration
7677

7778
authNormalize authz.NormalizeFunc
7879
authMap module.Table
@@ -251,6 +252,7 @@ func (endp *Endpoint) setConfig(cfg *config.Map) error {
251252
modconfig.Table(cfg, "auth_map", true, false, nil, &endp.saslAuth.AuthMap)
252253
cfg.Duration("write_timeout", false, false, 1*time.Minute, &endp.serv.WriteTimeout)
253254
cfg.Duration("read_timeout", false, false, 10*time.Minute, &endp.serv.ReadTimeout)
255+
cfg.Duration("shutdown_timeout", false, false, 3*time.Minute, &endp.shutdownTimeout)
254256
cfg.DataSize("max_message_size", false, false, 32*1024*1024, &endp.serv.MaxMessageBytes)
255257
cfg.DataSize("max_header_size", false, false, 1*1024*1024, &endp.maxHeaderBytes)
256258
cfg.Int("max_recipients", false, false, 20000, &endp.serv.MaxRecipients)
@@ -424,8 +426,13 @@ func (endp *Endpoint) ConnectionCount() int {
424426
}
425427

426428
func (endp *Endpoint) Stop() error {
427-
endp.serv.Close()
429+
ctx, cancel := context.WithTimeout(context.Background(), endp.shutdownTimeout)
430+
defer cancel()
431+
432+
endp.serv.Shutdown(ctx)
433+
428434
endp.listenersWg.Wait()
435+
429436
return nil
430437
}
431438

maddy.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"path/filepath"
2727
"runtime"
2828
"runtime/debug"
29+
"sync"
2930

3031
"github.com/caddyserver/certmagic"
3132
parser "github.com/foxcpp/maddy/framework/cfgparser"
@@ -206,6 +207,8 @@ func Run(c *cli.Context) error {
206207
defer log.DefaultLogger.Out.Close()
207208
defer hooks.RunHooks(hooks.EventShutdown)
208209

210+
hooks.AddHook(hooks.EventShutdown, netresource.CloseAllListeners)
211+
209212
if err := moduleMain(c.Path("config")); err != nil {
210213
systemdStatusErr(err)
211214
return cli.Exit(err.Error(), 1)
@@ -395,24 +398,29 @@ func moduleMain(configPath string) error {
395398
}
396399
c.DefaultLogger.Msg("server started", "version", Version)
397400

398-
systemdStatus(SDReady, "Listening for incoming connections...")
401+
systemdStatus(SDReady, "Configuration running.")
402+
asyncStopWg := sync.WaitGroup{} // Some containers might still be waiting on moduleStop
399403
for handleSignals() {
400-
systemdStatus(SDReloading, "Reloading state...")
401404
hooks.RunHooks(hooks.EventReload)
402405

403-
c = moduleReload(c, configPath)
406+
c = moduleReload(c, configPath, &asyncStopWg)
404407
}
405408

406409
c.DefaultLogger.Msg("server stopping...")
407-
systemdStatus(SDStopping, "Waiting for running transactions to complete...")
408410

411+
systemdStatus(SDStopping, "Waiting for old configuration to stop...")
412+
asyncStopWg.Wait()
413+
414+
systemdStatus(SDStopping, "Waiting for current configuration to stop...")
409415
moduleStop(c)
410416
c.DefaultLogger.Msg("server stopped")
417+
411418
return nil
412419
}
413420

414-
func moduleReload(oldContainer *container.C, configPath string) *container.C {
421+
func moduleReload(oldContainer *container.C, configPath string, asyncStopWg *sync.WaitGroup) *container.C {
415422
oldContainer.DefaultLogger.Msg("reloading server...")
423+
systemdStatus(SDReloading, "Reloading server...")
416424

417425
oldContainer.DefaultLogger.Msg("loading new configuration...")
418426
newContainer, err := moduleConfigure(configPath)
@@ -430,12 +438,22 @@ func moduleReload(oldContainer *container.C, configPath string) *container.C {
430438
container.Global = oldContainer
431439
return oldContainer
432440
}
433-
netresource.CloseUnusedListeners()
434441

435442
newContainer.DefaultLogger.Msg("server started", "version", Version)
436-
oldContainer.DefaultLogger.Msg("stopping server")
437-
moduleStop(oldContainer)
438-
oldContainer.DefaultLogger.Msg("server stopped")
443+
444+
systemdStatus(SDReloading, "New configuration running. Waiting for old connections and transactions to finish...")
445+
446+
asyncStopWg.Add(1)
447+
go func() {
448+
defer asyncStopWg.Done()
449+
defer netresource.CloseUnusedListeners()
450+
451+
oldContainer.DefaultLogger.Msg("stopping old server")
452+
moduleStop(oldContainer)
453+
oldContainer.DefaultLogger.Msg("old server stopped")
454+
455+
systemdStatus(SDReloading, "Configuration running.")
456+
}()
439457

440458
return newContainer
441459
}

0 commit comments

Comments
 (0)