@@ -280,10 +280,6 @@ public void uncaughtException(Thread t, Throwable e) {
280
280
// Must be mutated and read from constructor or syncContext
281
281
// used for channel tracing when value changed
282
282
private ManagedChannelServiceConfig lastServiceConfig = EMPTY_SERVICE_CONFIG ;
283
- // Must be mutated and read from constructor or syncContext
284
- // Denotes if the last resolved addresses were accepted by the load balancer. A {@code null}
285
- // value indicates no attempt has been made yet.
286
- private Boolean lastAddressesAccepted ;
287
283
288
284
@ Nullable
289
285
private final ManagedChannelServiceConfig defaultServiceConfig ;
@@ -371,6 +367,7 @@ private void shutdownNameResolverAndLoadBalancer(boolean channelIsActive) {
371
367
checkState (lbHelper != null , "lbHelper is null" );
372
368
}
373
369
if (nameResolver != null ) {
370
+ cancelNameResolverBackoff ();
374
371
nameResolver .shutdown ();
375
372
nameResolverStarted = false ;
376
373
if (channelIsActive ) {
@@ -453,10 +450,42 @@ private void rescheduleIdleTimer() {
453
450
idleTimer .reschedule (idleTimeoutMillis , TimeUnit .MILLISECONDS );
454
451
}
455
452
453
+ // Run from syncContext
454
+ @ VisibleForTesting
455
+ class DelayedNameResolverRefresh implements Runnable {
456
+ @ Override
457
+ public void run () {
458
+ scheduledNameResolverRefresh = null ;
459
+ refreshNameResolution ();
460
+ }
461
+ }
462
+
463
+ // Must be used from syncContext
464
+ @ Nullable private ScheduledHandle scheduledNameResolverRefresh ;
465
+ // The policy to control backoff between name resolution attempts. Non-null when an attempt is
466
+ // scheduled. Must be used from syncContext
467
+ @ Nullable private BackoffPolicy nameResolverBackoffPolicy ;
468
+
469
+ // Must be run from syncContext
470
+ private void cancelNameResolverBackoff () {
471
+ syncContext .throwIfNotInThisSynchronizationContext ();
472
+ if (scheduledNameResolverRefresh != null ) {
473
+ scheduledNameResolverRefresh .cancel ();
474
+ scheduledNameResolverRefresh = null ;
475
+ nameResolverBackoffPolicy = null ;
476
+ }
477
+ }
478
+
456
479
/**
457
- * Force name resolution refresh to happen immediately. Must be run
480
+ * Force name resolution refresh to happen immediately and reset refresh back-off . Must be run
458
481
* from syncContext.
459
482
*/
483
+ private void refreshAndResetNameResolution () {
484
+ syncContext .throwIfNotInThisSynchronizationContext ();
485
+ cancelNameResolverBackoff ();
486
+ refreshNameResolution ();
487
+ }
488
+
460
489
private void refreshNameResolution () {
461
490
syncContext .throwIfNotInThisSynchronizationContext ();
462
491
if (nameResolverStarted ) {
@@ -1261,7 +1290,7 @@ private void maybeTerminateChannel() {
1261
1290
// Must be called from syncContext
1262
1291
private void handleInternalSubchannelState (ConnectivityStateInfo newState ) {
1263
1292
if (newState .getState () == TRANSIENT_FAILURE || newState .getState () == IDLE ) {
1264
- refreshNameResolution ();
1293
+ refreshAndResetNameResolution ();
1265
1294
}
1266
1295
}
1267
1296
@@ -1308,9 +1337,9 @@ public void run() {
1308
1337
if (shutdown .get ()) {
1309
1338
return ;
1310
1339
}
1311
- if (lastAddressesAccepted != null && ! lastAddressesAccepted ) {
1340
+ if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh . isPending () ) {
1312
1341
checkState (nameResolverStarted , "name resolver must be started" );
1313
- refreshNameResolution ();
1342
+ refreshAndResetNameResolution ();
1314
1343
}
1315
1344
for (InternalSubchannel subchannel : subchannels ) {
1316
1345
subchannel .resetConnectBackoff ();
@@ -1466,7 +1495,7 @@ public void refreshNameResolution() {
1466
1495
final class LoadBalancerRefreshNameResolution implements Runnable {
1467
1496
@ Override
1468
1497
public void run () {
1469
- ManagedChannelImpl . this . refreshNameResolution ();
1498
+ refreshAndResetNameResolution ();
1470
1499
}
1471
1500
}
1472
1501
@@ -1707,7 +1736,7 @@ private final class NameResolverListener extends NameResolver.Listener2 {
1707
1736
}
1708
1737
1709
1738
@ Override
1710
- public boolean onResult (final ResolutionResult resolutionResult ) {
1739
+ public void onResult (final ResolutionResult resolutionResult ) {
1711
1740
final class NamesResolved implements Runnable {
1712
1741
1713
1742
@ SuppressWarnings ("ReferenceEquality" )
@@ -1716,7 +1745,6 @@ public void run() {
1716
1745
if (ManagedChannelImpl .this .nameResolver != resolver ) {
1717
1746
return ;
1718
1747
}
1719
- lastAddressesAccepted = false ;
1720
1748
1721
1749
List <EquivalentAddressGroup > servers = resolutionResult .getAddresses ();
1722
1750
channelLogger .log (
@@ -1730,6 +1758,7 @@ public void run() {
1730
1758
lastResolutionState = ResolutionState .SUCCESS ;
1731
1759
}
1732
1760
1761
+ nameResolverBackoffPolicy = null ;
1733
1762
ConfigOrError configOrError = resolutionResult .getServiceConfig ();
1734
1763
InternalConfigSelector resolvedConfigSelector =
1735
1764
resolutionResult .getAttributes ().get (InternalConfigSelector .KEY );
@@ -1787,7 +1816,6 @@ public void run() {
1787
1816
// we later check for these error codes when investigating pick results in
1788
1817
// GrpcUtil.getTransportFromPickResult().
1789
1818
onError (configOrError .getError ());
1790
- lastAddressesAccepted = false ;
1791
1819
return ;
1792
1820
} else {
1793
1821
effectiveServiceConfig = lastServiceConfig ;
@@ -1831,24 +1859,21 @@ public void run() {
1831
1859
}
1832
1860
Attributes attributes = attrBuilder .build ();
1833
1861
1834
- lastAddressesAccepted = helper .lb .tryAcceptResolvedAddresses (
1862
+ boolean addressesAccepted = helper .lb .tryAcceptResolvedAddresses (
1835
1863
ResolvedAddresses .newBuilder ()
1836
1864
.setAddresses (servers )
1837
1865
.setAttributes (attributes )
1838
1866
.setLoadBalancingPolicyConfig (effectiveServiceConfig .getLoadBalancingConfig ())
1839
1867
.build ());
1868
+
1869
+ if (!addressesAccepted ) {
1870
+ scheduleExponentialBackOffInSyncContext ();
1871
+ }
1840
1872
}
1841
1873
}
1842
1874
}
1843
1875
1844
1876
syncContext .execute (new NamesResolved ());
1845
-
1846
- // If NameResolved did not assign a value to lastAddressesAccepted, we assume there was an
1847
- // exception and set it to false.
1848
- if (lastAddressesAccepted == null ) {
1849
- lastAddressesAccepted = false ;
1850
- }
1851
- return lastAddressesAccepted ;
1852
1877
}
1853
1878
1854
1879
@ Override
@@ -1878,6 +1903,29 @@ private void handleErrorInSyncContext(Status error) {
1878
1903
}
1879
1904
1880
1905
helper .lb .handleNameResolutionError (error );
1906
+
1907
+ scheduleExponentialBackOffInSyncContext ();
1908
+ }
1909
+
1910
+ private void scheduleExponentialBackOffInSyncContext () {
1911
+ if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh .isPending ()) {
1912
+ // The name resolver may invoke onError multiple times, but we only want to
1913
+ // schedule one backoff attempt
1914
+ // TODO(ericgribkoff) Update contract of NameResolver.Listener or decide if we
1915
+ // want to reset the backoff interval upon repeated onError() calls
1916
+ return ;
1917
+ }
1918
+ if (nameResolverBackoffPolicy == null ) {
1919
+ nameResolverBackoffPolicy = backoffPolicyProvider .get ();
1920
+ }
1921
+ long delayNanos = nameResolverBackoffPolicy .nextBackoffNanos ();
1922
+ channelLogger .log (
1923
+ ChannelLogLevel .DEBUG ,
1924
+ "Scheduling DNS resolution backoff for {0} ns" , delayNanos );
1925
+ scheduledNameResolverRefresh =
1926
+ syncContext .schedule (
1927
+ new DelayedNameResolverRefresh (), delayNanos , TimeUnit .NANOSECONDS ,
1928
+ transportFactory .getScheduledExecutorService ());
1881
1929
}
1882
1930
}
1883
1931
0 commit comments