Skip to content

Commit 9b970fd

Browse files
authoredJun 7, 2024··
dns: fix constant 30s backoff for re-resolution (#7262) (#7311)
1 parent 6d23620 commit 9b970fd

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed
 

‎internal/resolver/dns/dns_resolver.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ var (
6363
func init() {
6464
resolver.Register(NewBuilder())
6565
internal.TimeAfterFunc = time.After
66+
internal.TimeNowFunc = time.Now
67+
internal.TimeUntilFunc = time.Until
6668
internal.NewNetResolver = newNetResolver
6769
internal.AddressDialer = addressDialer
6870
}
@@ -209,12 +211,12 @@ func (d *dnsResolver) watcher() {
209211
err = d.cc.UpdateState(*state)
210212
}
211213

212-
var waitTime time.Duration
214+
var nextResolutionTime time.Time
213215
if err == nil {
214216
// Success resolving, wait for the next ResolveNow. However, also wait 30
215217
// seconds at the very least to prevent constantly re-resolving.
216218
backoffIndex = 1
217-
waitTime = MinResolutionInterval
219+
nextResolutionTime = internal.TimeNowFunc().Add(MinResolutionInterval)
218220
select {
219221
case <-d.ctx.Done():
220222
return
@@ -223,13 +225,13 @@ func (d *dnsResolver) watcher() {
223225
} else {
224226
// Poll on an error found in DNS Resolver or an error received from
225227
// ClientConn.
226-
waitTime = backoff.DefaultExponential.Backoff(backoffIndex)
228+
nextResolutionTime = internal.TimeNowFunc().Add(backoff.DefaultExponential.Backoff(backoffIndex))
227229
backoffIndex++
228230
}
229231
select {
230232
case <-d.ctx.Done():
231233
return
232-
case <-internal.TimeAfterFunc(waitTime):
234+
case <-internal.TimeAfterFunc(internal.TimeUntilFunc(nextResolutionTime)):
233235
}
234236
}
235237
}

‎internal/resolver/dns/dns_resolver_test.go

+78
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,27 @@ func overrideTimeAfterFuncWithChannel(t *testing.T) (durChan chan time.Duration,
102102
return durChan, timeChan
103103
}
104104

105+
// Override the current time used by the DNS resolver.
106+
func overrideTimeNowFunc(t *testing.T, now time.Time) {
107+
origTimeNowFunc := dnsinternal.TimeNowFunc
108+
dnsinternal.TimeNowFunc = func() time.Time { return now }
109+
t.Cleanup(func() { dnsinternal.TimeNowFunc = origTimeNowFunc })
110+
}
111+
112+
// Override the remaining wait time to allow re-resolution by DNS resolver.
113+
// Use the timeChan to read the time until resolver needs to wait for
114+
// and return 0 wait time.
115+
func overrideTimeUntilFuncWithChannel(t *testing.T) (timeChan chan time.Time) {
116+
timeCh := make(chan time.Time, 1)
117+
origTimeUntil := dnsinternal.TimeUntilFunc
118+
dnsinternal.TimeUntilFunc = func(t time.Time) time.Duration {
119+
timeCh <- t
120+
return 0
121+
}
122+
t.Cleanup(func() { dnsinternal.TimeUntilFunc = origTimeUntil })
123+
return timeCh
124+
}
125+
105126
func enableSRVLookups(t *testing.T) {
106127
origEnableSRVLookups := dns.EnableSRVLookups
107128
dns.EnableSRVLookups = true
@@ -1290,3 +1311,60 @@ func (s) TestMinResolutionInterval(t *testing.T) {
12901311
r.ResolveNow(resolver.ResolveNowOptions{})
12911312
}
12921313
}
1314+
1315+
// TestMinResolutionInterval_NoExtraDelay verifies that there is no extra delay
1316+
// between two resolution requests apart from [MinResolutionInterval].
1317+
func (s) TestMinResolutionInterval_NoExtraDelay(t *testing.T) {
1318+
tr := &testNetResolver{
1319+
hostLookupTable: map[string][]string{
1320+
"foo.bar.com": {"1.2.3.4", "5.6.7.8"},
1321+
},
1322+
txtLookupTable: map[string][]string{
1323+
"_grpc_config.foo.bar.com": txtRecordServiceConfig(txtRecordGood),
1324+
},
1325+
}
1326+
overrideNetResolver(t, tr)
1327+
// Override time.Now() to return a zero value for time. This will allow us
1328+
// to verify that the call to time.Until is made with the exact
1329+
// [MinResolutionInterval] that we expect.
1330+
overrideTimeNowFunc(t, time.Time{})
1331+
// Override time.Until() to read the time passed to it
1332+
// and return immediately without any delay
1333+
timeCh := overrideTimeUntilFuncWithChannel(t)
1334+
1335+
r, stateCh, errorCh := buildResolverWithTestClientConn(t, "foo.bar.com")
1336+
1337+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
1338+
defer cancel()
1339+
1340+
// Ensure that the first resolution happens.
1341+
select {
1342+
case <-ctx.Done():
1343+
t.Fatal("Timeout when waiting for DNS resolver")
1344+
case err := <-errorCh:
1345+
t.Fatalf("Unexpected error from resolver, %v", err)
1346+
case <-stateCh:
1347+
}
1348+
1349+
// Request re-resolution and verify that the resolver waits for
1350+
// [MinResolutionInterval].
1351+
r.ResolveNow(resolver.ResolveNowOptions{})
1352+
select {
1353+
case <-ctx.Done():
1354+
t.Fatal("Timeout when waiting for DNS resolver")
1355+
case gotTime := <-timeCh:
1356+
wantTime := time.Time{}.Add(dns.MinResolutionInterval)
1357+
if !gotTime.Equal(wantTime) {
1358+
t.Fatalf("DNS resolver waits for %v time before re-resolution, want %v", gotTime, wantTime)
1359+
}
1360+
}
1361+
1362+
// Ensure that the re-resolution request actually happens.
1363+
select {
1364+
case <-ctx.Done():
1365+
t.Fatal("Timeout when waiting for an error from the resolver")
1366+
case err := <-errorCh:
1367+
t.Fatalf("Unexpected error from resolver, %v", err)
1368+
case <-stateCh:
1369+
}
1370+
}

‎internal/resolver/dns/internal/internal.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,22 @@ var (
5151
// The following vars are overridden from tests.
5252
var (
5353
// TimeAfterFunc is used by the DNS resolver to wait for the given duration
54-
// to elapse. In non-test code, this is implemented by time.After. In test
54+
// to elapse. In non-test code, this is implemented by time.After. In test
5555
// code, this can be used to control the amount of time the resolver is
5656
// blocked waiting for the duration to elapse.
5757
TimeAfterFunc func(time.Duration) <-chan time.Time
5858

59+
// TimeNowFunc is used by the DNS resolver to get the current time.
60+
// In non-test code, this is implemented by time.Now. In test code,
61+
// this can be used to control the current time for the resolver.
62+
TimeNowFunc func() time.Time
63+
64+
// TimeUntilFunc is used by the DNS resolver to calculate the remaining
65+
// wait time for re-resolution. In non-test code, this is implemented by
66+
// time.Until. In test code, this can be used to control the remaining
67+
// time for resolver to wait for re-resolution.
68+
TimeUntilFunc func(time.Time) time.Duration
69+
5970
// NewNetResolver returns the net.Resolver instance for the given target.
6071
NewNetResolver func(string) (NetResolver, error)
6172

0 commit comments

Comments
 (0)
Please sign in to comment.