resolver: a query cut short by the caller's context records no reason (closes #298)
check / check (push) Canceled after 0s

When the caller's context ends, the next query returns
ErrContextCanceled before anything is sent. querySingleType recorded
that as a network error, which outranks a timeout, so a nameserver that
had only timed out could be reported as "network error: context
canceled". It now records no reason for it.

Model: opus-5-5
This commit is contained in:
2026-10-15 16:38:24 +00:00
parent 06ae357165
commit 5f20fa31c8
3 changed files with 39 additions and 0 deletions
@@ -1,6 +1,8 @@
package resolver
import (
"context"
"log/slog"
"strconv"
"syscall"
"testing"
@@ -132,3 +134,33 @@ func TestReadReply(t *testing.T) {
})
}
}
// TestQuerySingleType_ContextEnded asks a nameserver about one record
// type with a context that has already ended, so no query is sent. A
// query the context cut short says nothing about the nameserver and
// records no reason: a nameserver whose earlier query timed out is
// still reported as timed out, not as a network error.
func TestQuerySingleType_ContextEnded(t *testing.T) {
t.Parallel()
r := NewFromLogger(slog.New(slog.DiscardHandler))
ctx, cancel := context.WithCancel(t.Context())
cancel()
resp := &NameserverResponse{Records: map[string][]string{}, Status: StatusOK}
// The query for an earlier record type timed out.
state := queryState{gotTimeout: true}
_, err := r.querySingleType(
ctx, []string{"192.0.2.1"}, "example.com.", dns.TypeA, resp, &state,
)
require.ErrorIs(t, err, ErrContextCanceled)
assert.Equal(t, queryState{gotTimeout: true}, state)
classifyResponse(resp, state)
assert.Equal(t, StatusTimeout, resp.Status)
assert.Equal(t, "all queries timed out", resp.Error)
}