The initial upstream exchange ignored its supplied timeout and used hardcoded client limits. Bound each attempt with a context, copy the shared client for its timeout, and share the attempt budget with UDP-to-TCP fallback. Close private connections on cancellation to interrupt reads, and exclude caller cancellation from upstream failures so parallel losers remain healthy. The normal engine pipeline never applied server.timeouts.query, allowing failover to outlive the configured query budget. Apply a hot-reloaded overall context deadline at Handle while retaining earlier caller deadlines. Add silent UDP/TCP/TLS, cancellation, fallback, failover, and reload regressions. The server inflight test assumed receiving a TCP reply meant its sent counter had already been incremented. Wait for both completion counters before asserting them, preserving the existing assertions while removing the scheduling race.
This commit is contained in:
1 parent
32b214fac0
commit
340b4eaf80
6 files changed
+233
-10
No files matched your search
@@ -135,6 +135,8 @@ type Engine struct {
|
||||
|
||||
// upstreamTimeout bounds a single upstream exchange.
|
||||
upstreamTimeout atomic.Int64
|
||||
// queryTimeout bounds the entire foreground pipeline, including failover.
|
||||
queryTimeout atomic.Int64
|
||||
|
||||
// groupStats tracks per-group counters for the UI.
|
||||
groupStats *groupStatTable
|
||||
@@ -212,6 +214,7 @@ func (e *Engine) applyConfig(cfg *config.Config) {
|
||||
e.sem.Store(&sem)
|
||||
e.semSize.Store(int64(semSize))
|
||||
e.upstreamTimeout.Store(int64(cfg.Server.Timeouts.Upstre.D()))
|
||||
e.queryTimeout.Store(int64(cfg.Server.Timeouts.Query.D()))
|
||||
|
||||
if e.aclH != nil {
|
||||
e.aclH.Set(acl.Compile(cfg.ACL))
|
||||
@@ -254,6 +257,11 @@ func (e *Engine) GroupStats() []acl.GroupSummary {
|
||||
// Handle runs the full pipeline for one request and returns the response to
|
||||
// send. It never returns nil: a synthetic SERVFAIL is the worst case.
|
||||
func (e *Engine) Handle(ctx context.Context, req Request) Response {
|
||||
if timeout := time.Duration(e.queryTimeout.Load()); timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
}
|
||||
start := req.Start
|
||||
if start.IsZero() {
|
||||
start = time.Now()
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.k8s-git-k6n.net/mats/fancy-dns/internal/config"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestQueryDeadlineAcrossFailoverAndReload(t *testing.T) {
|
||||
var servers []config.UpstreamServer
|
||||
for i := 0; i < 3; i++ {
|
||||
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = pc.Close() })
|
||||
servers = append(servers, config.UpstreamServer{Addr: pc.LocalAddr().String(), Proto: "udp"})
|
||||
}
|
||||
h := newHarness(t, hopt{upstreams: servers, timeout: 100 * time.Millisecond, server: func(s *config.Server) { s.Timeouts.Query = config.Duration(150 * time.Millisecond) }})
|
||||
cfg := h.pool.Config()
|
||||
cfg.Policy = "failover"
|
||||
cfg.MaxAttempts = 3
|
||||
h.pool.Rebind(cfg)
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
query, caller, limit time.Duration
|
||||
}{
|
||||
{"configured", 150 * time.Millisecond, time.Second, 250 * time.Millisecond},
|
||||
{"reload", 40 * time.Millisecond, time.Second, 90 * time.Millisecond},
|
||||
{"earlier-caller", time.Second, 30 * time.Millisecond, 90 * time.Millisecond},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
next := *h.cfg
|
||||
next.Server.Timeouts.Query = config.Duration(tc.query)
|
||||
if err := h.store.Swap(&next); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tc.caller)
|
||||
defer cancel()
|
||||
start := time.Now()
|
||||
res := h.eng.Handle(ctx, h.request(tc.name+".test.", dns.TypeA))
|
||||
requireRcode(t, res, dns.RcodeServerFailure)
|
||||
if elapsed := time.Since(start); elapsed > tc.limit {
|
||||
t.Fatalf("query took %v, limit %v", elapsed, tc.limit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user