upstream: one absolute per-query budget across queueing and failover

waiting for a slot now spends the query budget; truncated attempts that
expire fault the budget, not the upstream, and are never attributed.
admission sweeps in priority order before blocking. forward zones spend
read_timeout_ms once across udp, truncation and tcp. adds
nxdns_upstream_budget_exhausted_total and a 64-upstream validation limit.
This commit is contained in:
2026-08-27 21:10:43 +02:00
parent 6170571d23
commit 72cdbbd113
16 changed files with 1598 additions and 207 deletions
+11 -2
View File
@@ -676,7 +676,10 @@ const Context = struct {
const answer = client.exchange(ctx.io, ctx.query, ctx.response_buf) catch |err| {
return switch (transport.group(err)) {
.cancellation => .drop,
.peer_fault, .local_resource => ctx.servFail(),
// A budget that ran out is SERVFAIL like any other failure: no
// rcode says "I gave up in time". It is not logged per query —
// `nxdns_upstream_budget_exhausted_total` is the record.
.peer_fault, .local_resource, .budget_exhausted => ctx.servFail(),
};
};
@@ -737,7 +740,13 @@ const Context = struct {
// the client is about to lose the socket anyway; the listener's
// own counters record the abandoned datagram.
.cancellation => return .drop,
.peer_fault, .local_resource => return ctx.servFail(),
// A budget that ran out is SERVFAIL like any other failure: no
// rcode says "I gave up in time". It is not logged per query —
// `nxdns_upstream_budget_exhausted_total` is the record — and
// the pool leaves `selected` unchanged, so the row still names
// the last attributable endpoint if there was one, and names
// none only when no attributable attempt happened.
.peer_fault, .local_resource, .budget_exhausted => return ctx.servFail(),
};
};
bump(&ctx.handler.stats.queries);