plugin/secondary: stop update loop on reload shutdown (#8198)

This commit is contained in:
houyuwushang
2026-07-02 21:46:03 +08:00
committed by GitHub
parent 3a4b87bdb4
commit 679f764ae1
3 changed files with 90 additions and 19 deletions

View File

@@ -116,11 +116,16 @@ func less(a, b uint32) bool {
func (z *Zone) Update(updateShutdown chan bool, t *transfer.Transfer) error { func (z *Zone) Update(updateShutdown chan bool, t *transfer.Transfer) error {
// If we don't have a SOA, we don't have a zone, wait for it to appear. // If we don't have a SOA, we don't have a zone, wait for it to appear.
for z.getSOA() == nil { for z.getSOA() == nil {
time.Sleep(1 * time.Second) if waitOrShutdown(updateShutdown, time.Second) {
return nil
}
} }
retryActive := false retryActive := false
Restart: Restart:
if updateStopped(updateShutdown) {
return nil
}
soa := z.getSOA() soa := z.getSOA()
refresh := time.Second * time.Duration(soa.Refresh) refresh := time.Second * time.Duration(soa.Refresh)
retry := time.Second * time.Duration(soa.Retry) retry := time.Second * time.Duration(soa.Retry)
@@ -145,7 +150,10 @@ Restart:
break break
} }
time.Sleep(jitter(2000)) // 2s randomize if waitOrShutdown(updateShutdown, jitter(2000)) { // 2s randomize
stopUpdateTickers(refreshTicker, retryTicker, expireTicker)
return nil
}
ok, err := z.shouldTransfer() ok, err := z.shouldTransfer()
if err != nil { if err != nil {
@@ -162,14 +170,15 @@ Restart:
// no errors, stop timers and restart // no errors, stop timers and restart
retryActive = false retryActive = false
refreshTicker.Stop() stopUpdateTickers(refreshTicker, retryTicker, expireTicker)
retryTicker.Stop()
expireTicker.Stop()
goto Restart goto Restart
case <-refreshTicker.C: case <-refreshTicker.C:
time.Sleep(jitter(5000)) // 5s randomize if waitOrShutdown(updateShutdown, jitter(5000)) { // 5s randomize
stopUpdateTickers(refreshTicker, retryTicker, expireTicker)
return nil
}
ok, err := z.shouldTransfer() ok, err := z.shouldTransfer()
if err != nil { if err != nil {
@@ -188,20 +197,45 @@ Restart:
// no errors, stop timers and restart // no errors, stop timers and restart
retryActive = false retryActive = false
refreshTicker.Stop() stopUpdateTickers(refreshTicker, retryTicker, expireTicker)
retryTicker.Stop()
expireTicker.Stop()
goto Restart goto Restart
case <-updateShutdown: case <-updateShutdown:
refreshTicker.Stop() stopUpdateTickers(refreshTicker, retryTicker, expireTicker)
retryTicker.Stop()
expireTicker.Stop()
return nil return nil
} }
} }
} }
func waitOrShutdown(updateShutdown <-chan bool, d time.Duration) bool {
if updateStopped(updateShutdown) {
return true
}
timer := time.NewTimer(d)
defer timer.Stop()
select {
case <-timer.C:
return false
case <-updateShutdown:
return true
}
}
func updateStopped(updateShutdown <-chan bool) bool {
select {
case <-updateShutdown:
return true
default:
return false
}
}
func stopUpdateTickers(tickers ...*time.Ticker) {
for _, ticker := range tickers {
ticker.Stop()
}
}
// jitter returns a random duration between [0,n) * time.Millisecond // jitter returns a random duration between [0,n) * time.Millisecond
func jitter(n int) time.Duration { func jitter(n int) time.Duration {
r := rand.Intn(n) // #nosec G404 -- non-cryptographic jitter to spread transfer attempts. r := rand.Intn(n) // #nosec G404 -- non-cryptographic jitter to spread transfer attempts.

View File

@@ -3,6 +3,7 @@ package file
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/coredns/coredns/plugin/pkg/dnstest" "github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test" "github.com/coredns/coredns/plugin/test"
@@ -121,6 +122,27 @@ func TestTransferIn(t *testing.T) {
} }
} }
func TestUpdateStopsBeforeInitialTransfer(t *testing.T) {
z := NewZone(testZone, "test")
updateShutdown := make(chan bool)
done := make(chan struct{})
go func() {
if err := z.Update(updateShutdown, nil); err != nil {
t.Errorf("Unexpected update error: %v", err)
}
close(done)
}()
close(updateShutdown)
select {
case <-done:
case <-time.After(200 * time.Millisecond):
t.Fatal("Update did not stop while waiting for initial SOA")
}
}
func TestIsNotify(t *testing.T) { func TestIsNotify(t *testing.T) {
z := new(Zone) z := new(Zone)
z.origin = testZone z.origin = testZone

View File

@@ -1,6 +1,7 @@
package secondary package secondary
import ( import (
"sync"
"time" "time"
"github.com/coredns/caddy" "github.com/coredns/caddy"
@@ -42,6 +43,7 @@ func setup(c *caddy.Controller) error {
if len(z.TransferFrom) > 0 { if len(z.TransferFrom) > 0 {
// In order to support secondary plugin reloading. // In order to support secondary plugin reloading.
updateShutdown := make(chan bool) updateShutdown := make(chan bool)
var updateShutdownOnce sync.Once
c.OnStartup(func() error { c.OnStartup(func() error {
z.StartupOnce.Do(func() { z.StartupOnce.Do(func() {
@@ -54,24 +56,26 @@ func setup(c *caddy.Controller) error {
break break
} }
log.Warningf("All '%s' masters failed to transfer, retrying in %s: %s", n, dur.String(), err) log.Warningf("All '%s' masters failed to transfer, retrying in %s: %s", n, dur.String(), err)
time.Sleep(dur) if waitForTransferRetry(updateShutdown, dur) {
return
}
dur <<= 1 // double the duration dur <<= 1 // double the duration
if dur > max { if dur > max {
dur = max dur = max
} }
}
select { select {
case <-updateShutdown: case <-updateShutdown:
return return
default: default:
} }
}
z.Update(updateShutdown, x) z.Update(updateShutdown, x)
}() }()
}) })
return nil return nil
}) })
c.OnShutdown(func() error { c.OnShutdown(func() error {
updateShutdown <- true updateShutdownOnce.Do(func() { close(updateShutdown) })
return nil return nil
}) })
} }
@@ -85,6 +89,17 @@ func setup(c *caddy.Controller) error {
return nil return nil
} }
func waitForTransferRetry(updateShutdown <-chan bool, dur time.Duration) bool {
timer := time.NewTimer(dur)
defer timer.Stop()
select {
case <-timer.C:
return false
case <-updateShutdown:
return true
}
}
func secondaryParse(c *caddy.Controller) (file.Zones, fall.F, error) { func secondaryParse(c *caddy.Controller) (file.Zones, fall.F, error) {
z := make(map[string]*file.Zone) z := make(map[string]*file.Zone)
names := []string{} names := []string{}