Skip to content

Commit a9fed48

Browse files
committed
Fail RetryingNameResolver if double-wrapped
1 parent 31dcdc3 commit a9fed48

File tree

2 files changed

+40
-8
lines changed

2 files changed

+40
-8
lines changed

core/src/main/java/io/grpc/internal/RetryingNameResolver.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ private class RetryingListener extends Listener2 {
8484

8585
@Override
8686
public void onResult(ResolutionResult resolutionResult) {
87+
// If the resolution result listener is already an attribute it indicates that a name resolver
88+
// has already been wrapped with this class. This indicates a misconfiguration.
89+
if (resolutionResult.getAttributes().get(RESOLUTION_RESULT_LISTENER_KEY) != null) {
90+
throw new IllegalStateException(
91+
"RetryingNameResolver can only be used once to wrap a NameResolver");
92+
}
93+
8794
delegateListener.onResult(resolutionResult.toBuilder().setAttributes(
8895
resolutionResult.getAttributes().toBuilder()
8996
.set(RESOLUTION_RESULT_LISTENER_KEY, new ResolutionResultListener()).build())

core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package io.grpc.internal;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.fail;
2021
import static org.mockito.ArgumentMatchers.isA;
2122
import static org.mockito.Mockito.verify;
2223

2324
import io.grpc.NameResolver;
2425
import io.grpc.NameResolver.Listener2;
2526
import io.grpc.NameResolver.ResolutionResult;
27+
import io.grpc.Status;
2628
import io.grpc.internal.RetryingNameResolver.ResolutionResultListener;
2729
import org.junit.Before;
2830
import org.junit.Rule;
@@ -53,7 +55,7 @@ public class RetryingNameResolverTest {
5355
@Captor
5456
private ArgumentCaptor<Listener2> listenerCaptor;
5557
@Captor
56-
private ArgumentCaptor<ResolutionResult> resolutionResultCaptor;
58+
private ArgumentCaptor<ResolutionResult> onResultCaptor;
5759

5860
private RetryingNameResolver retryingNameResolver;
5961

@@ -69,15 +71,15 @@ public void startAndShutdown() {
6971
}
7072

7173
// Make sure the ResolutionResultListener callback is added to the ResolutionResult attributes,
72-
// and the retry scheduler is reset since the name resolution was successful..
74+
// and the retry scheduler is reset since the name resolution was successful.
7375
@Test
7476
public void onResult_sucess() {
7577
retryingNameResolver.start(mockListener);
7678
verify(mockNameResolver).start(listenerCaptor.capture());
7779

7880
listenerCaptor.getValue().onResult(ResolutionResult.newBuilder().build());
79-
verify(mockListener).onResult(resolutionResultCaptor.capture());
80-
ResolutionResultListener resolutionResultListener = resolutionResultCaptor.getValue()
81+
verify(mockListener).onResult(onResultCaptor.capture());
82+
ResolutionResultListener resolutionResultListener = onResultCaptor.getValue()
8183
.getAttributes()
8284
.get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY);
8385
assertThat(resolutionResultListener).isNotNull();
@@ -87,15 +89,15 @@ public void onResult_sucess() {
8789
}
8890

8991
// Make sure the ResolutionResultListener callback is added to the ResolutionResult attributes,
90-
// and the retry scheduler is reset since the name resolution was successful..
92+
// and that a retry gets scheduled when the resolution results are rejected.
9193
@Test
9294
public void onResult_failure() {
9395
retryingNameResolver.start(mockListener);
9496
verify(mockNameResolver).start(listenerCaptor.capture());
9597

9698
listenerCaptor.getValue().onResult(ResolutionResult.newBuilder().build());
97-
verify(mockListener).onResult(resolutionResultCaptor.capture());
98-
ResolutionResultListener resolutionResultListener = resolutionResultCaptor.getValue()
99+
verify(mockListener).onResult(onResultCaptor.capture());
100+
ResolutionResultListener resolutionResultListener = onResultCaptor.getValue()
99101
.getAttributes()
100102
.get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY);
101103
assertThat(resolutionResultListener).isNotNull();
@@ -104,8 +106,31 @@ public void onResult_failure() {
104106
verify(mockRetryScheduler).schedule(isA(Runnable.class));
105107
}
106108

109+
// Wrapping a NameResolver more than once is a misconfiguration.
107110
@Test
108-
public void onError() {
111+
public void onResult_failure_doubleWrapped() {
112+
NameResolver doubleWrappedResolver = new RetryingNameResolver(retryingNameResolver,
113+
mockRetryScheduler);
114+
115+
doubleWrappedResolver.start(mockListener);
116+
verify(mockNameResolver).start(listenerCaptor.capture());
117+
118+
try {
119+
listenerCaptor.getValue().onResult(ResolutionResult.newBuilder().build());
120+
} catch (IllegalStateException e) {
121+
assertThat(e).hasMessageThat().contains("can only be used once");
122+
return;
123+
}
124+
fail("An exception should have been thrown for a double wrapped NAmeResolver");
125+
}
109126

127+
// A retry should get scheduled when name resolution fails.
128+
@Test
129+
public void onError() {
130+
retryingNameResolver.start(mockListener);
131+
verify(mockNameResolver).start(listenerCaptor.capture());
132+
listenerCaptor.getValue().onError(Status.DEADLINE_EXCEEDED);
133+
verify(mockListener).onError(Status.DEADLINE_EXCEEDED);
134+
verify(mockRetryScheduler).schedule(isA(Runnable.class));
110135
}
111136
}

0 commit comments

Comments
 (0)