From 8f7b2a66ea3ad55c46a369755a010048e01b1bdd Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 30 Sep 2026 01:18:21 -0700 Subject: [PATCH] plugin/cache: Fix timed stale caused refreshes (#8587) --- plugin/cache/handler.go | 6 ++-- plugin/cache/serve_stale_recheck_test.go | 43 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/plugin/cache/handler.go b/plugin/cache/handler.go index f861fa83f..007a3e2a2 100644 --- a/plugin/cache/handler.go +++ b/plugin/cache/handler.go @@ -54,7 +54,7 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) if c.verifyStale { failureRecheck := c.staleRecheck nowFunc := c.now - trackRefresh := failureRecheck > 0 + trackRefresh := failureRecheck > 0 || c.verifyStaleTimeout > 0 if !trackRefresh || i.beginRefresh(now, failureRecheck) { refreshState := authenticatedRefreshState(state) crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: refreshState, server: server, do: do, ad: ad, cd: cd} @@ -187,9 +187,7 @@ func (c *Cache) verifyWithTimeout(ctx context.Context, state request.Request, w } go func() { rc, re := c.doRefresh(refreshCtx, state, cw) - if failureRecheck > 0 { - i.endRefresh(now(), failureRecheck, cw.refreshed) - } + i.endRefresh(now(), failureRecheck, cw.refreshed) done <- result{rc, re} }() timer := time.NewTimer(c.verifyStaleTimeout) diff --git a/plugin/cache/serve_stale_recheck_test.go b/plugin/cache/serve_stale_recheck_test.go index 5e39c51f6..cff2c8cdc 100644 --- a/plugin/cache/serve_stale_recheck_test.go +++ b/plugin/cache/serve_stale_recheck_test.go @@ -299,6 +299,49 @@ func TestServeStaleFailureRecheckVerifyTimeoutCoalesces(t *testing.T) { } } +func TestServeStaleVerifyTimeoutCoalescesWithoutFailureRecheck(t *testing.T) { + clock := newStaleRecheckClock() + c := New() + c.now = clock.Now + c.minpttl = 0 + c.minnttl = 0 + c.staleUpTo = time.Hour + c.verifyStale = true + c.verifyStaleTimeout = 10 * time.Millisecond + c.Next = ttlBackend(1) + + req := new(dns.Msg) + req.SetQuestion("cached.org.", dns.TypeA) + serveStaleRecheckRequest(t, c, req) + item := c.exists("cached.org.", dns.TypeA, dns.ClassINET, false, false) + clock.Set(2 * time.Second) + + var calls atomic.Int32 + started := make(chan struct{}, 2) + completed := make(chan struct{}, 2) + release := make(chan struct{}) + defer close(release) + failure := servFailBackend(30) + c.Next = plugin.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + calls.Add(1) + started <- struct{}{} + <-release + rcode, err := failure.ServeDNS(ctx, w, r) + completed <- struct{}{} + return rcode, err + }) + + serveStaleRecheckRequest(t, c, req) + waitForStaleSignal(t, started, "background verify did not start") + serveStaleRecheckRequest(t, c, req) + if got := calls.Load(); got != 1 { + t.Fatalf("expected concurrent stale request to share the in-flight verify, got %d attempts", got) + } + + release <- struct{}{} + waitForStaleRefresh(t, completed, item) +} + func TestServeStaleFailureRecheckDisabledPreservesVerifyBehavior(t *testing.T) { clock := newStaleRecheckClock() c := New()