Handle empty address list from NameResolver.

Passing an empty list to NameResolver.Listener.onUpdate() will trigger
onError(). It is documented in the javadoc and enforced by
ManagedChannelImpl.

Forbid empty address list in EquivalentAddressGroup.

Resolves #1657
This commit is contained in:
Kun Zhang 2016-04-14 11:17:30 -07:00
parent 51fd870cfd
commit 7659f6ed68
5 changed files with 35 additions and 1 deletions

View File

@ -31,6 +31,8 @@
package io.grpc;
import com.google.common.base.Preconditions;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
@ -49,6 +51,7 @@ public final class EquivalentAddressGroup {
private final List<SocketAddress> addrs;
public EquivalentAddressGroup(List<SocketAddress> addrs) {
Preconditions.checkArgument(!addrs.isEmpty(), "addrs is empty");
this.addrs = Collections.unmodifiableList(new ArrayList<SocketAddress>(addrs));
}

View File

@ -68,6 +68,9 @@ public abstract class LoadBalancer<T> {
* Handles newly resolved addresses and service config from name resolution system.
*
* <p>Implementations should not modify the given {@code servers}.
*
* @param servers the resolved server addresses. Never empty.
* @param config extra configuration data from naming system.
*/
public void handleResolvedAddresses(List<ResolvedServerInfo> servers, Attributes config) { }

View File

@ -119,6 +119,9 @@ public abstract class NameResolver {
* Handles updates on resolved addresses and config.
*
* <p>Implementations will not modify the given {@code servers}.
*
* @param servers the resolved server addresses. An empty list will trigger {@link #onError}
* @param config extra configuration data from naming system
*/
void onUpdate(List<ResolvedServerInfo> servers, Attributes config);

View File

@ -168,7 +168,11 @@ public final class ManagedChannelImpl extends ManagedChannel implements WithLogI
this.nameResolver.start(new NameResolver.Listener() {
@Override
public void onUpdate(List<ResolvedServerInfo> servers, Attributes config) {
loadBalancer.handleResolvedAddresses(servers, config);
if (servers.isEmpty()) {
onError(Status.UNAVAILABLE.withDescription("NameResolver returned an empty list"));
} else {
loadBalancer.handleResolvedAddresses(servers, config);
}
}
@Override

View File

@ -362,6 +362,27 @@ public class ManagedChannelImplTest {
Status status = statusCaptor.getValue();
assertSame(error.getCode(), status.getCode());
assertSame(error.getCause(), status.getCause());
assertEquals(1, loadBalancerFactory.balancers.size());
verify(loadBalancerFactory.balancers.get(0)).handleNameResolutionError(same(error));
}
@Test
public void nameResolverReturnsEmptyList() {
String errorDescription = "NameResolver returned an empty list";
ManagedChannel channel = createChannel(
new FakeNameResolverFactory(new ArrayList<ResolvedServerInfo>()), NO_INTERCEPTOR);
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata());
ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
verify(mockCallListener, timeout(1000)).onClose(statusCaptor.capture(), any(Metadata.class));
Status status = statusCaptor.getValue();
assertSame(Status.Code.UNAVAILABLE, status.getCode());
assertTrue(status.getDescription(), status.getDescription().contains(errorDescription));
assertEquals(1, loadBalancerFactory.balancers.size());
verify(loadBalancerFactory.balancers.get(0)).handleNameResolutionError(statusCaptor.capture());
status = statusCaptor.getValue();
assertSame(Status.Code.UNAVAILABLE, status.getCode());
assertEquals(errorDescription, status.getDescription());
}
@Test