fix: prevent SIGTERM/reload deadlock (#7562)

This commit is contained in:
Ville Vesilehto
2025-09-19 14:01:53 +03:00
committed by GitHub
parent 5532ba8484
commit 6ec327836b
6 changed files with 171 additions and 24 deletions

View File

@@ -50,6 +50,10 @@ type Server struct {
classChaos bool // allow non-INET class queries
tsigSecret map[string]string
// Ensure Stop is idempotent when invoked concurrently (e.g., during reload and SIGTERM).
stopOnce sync.Once
wgDoneOnce sync.Once
}
// MetadataCollector is a plugin that can retrieve metadata functions from all metadata providing plugins
@@ -212,33 +216,37 @@ func (s *Server) ListenPacket() (net.PacketConn, error) {
// immediately.
// This implements Caddy.Stopper interface.
func (s *Server) Stop() (err error) {
if runtime.GOOS != "windows" {
// force connections to close after timeout
done := make(chan struct{})
go func() {
s.dnsWg.Done() // decrement our initial increment used as a barrier
s.dnsWg.Wait()
close(done)
}()
var onceErr error
s.stopOnce.Do(func() {
if runtime.GOOS != "windows" {
// force connections to close after timeout
done := make(chan struct{})
go func() {
// decrement our initial increment used as a barrier, but only once
s.wgDoneOnce.Do(func() { s.dnsWg.Done() })
s.dnsWg.Wait()
close(done)
}()
// Wait for remaining connections to finish or
// force them all to close after timeout
select {
case <-time.After(s.graceTimeout):
case <-done:
// Wait for remaining connections to finish or
// force them all to close after timeout
select {
case <-time.After(s.graceTimeout):
case <-done:
}
}
}
// Close the listener now; this stops the server without delay
s.m.Lock()
for _, s1 := range s.server {
// We might not have started and initialized the full set of servers
if s1 != nil {
err = s1.Shutdown()
// Close the listener now; this stops the server without delay
s.m.Lock()
for _, s1 := range s.server {
// We might not have started and initialized the full set of servers
if s1 != nil {
onceErr = s1.Shutdown()
}
}
}
s.m.Unlock()
return
s.m.Unlock()
})
return onceErr
}
// Address together with Stop() implement caddy.GracefulServer.

View File

@@ -2,6 +2,7 @@ package dnsserver
import (
"context"
"sync"
"testing"
"github.com/coredns/coredns/plugin"
@@ -120,3 +121,22 @@ func BenchmarkCoreServeDNS(b *testing.B) {
s.ServeDNS(ctx, w, m)
}
}
// Validates Stop is idempotent and safe under concurrent calls.
func TestStopIsIdempotent(t *testing.T) {
t.Parallel()
s := &Server{}
s.dnsWg.Add(1)
const n = 10
var wg sync.WaitGroup
wg.Add(n)
for range n {
go func() {
defer wg.Done()
_ = s.Stop()
}()
}
wg.Wait()
}