core: fix NPE in ConfigSelectingClientCall

Fix the following bug:

ManagedChannelImpl.ConfigSelectingClientCall may return early in start() leaving delegate null, and fails request() method after start().

Currently the bug can only be triggered when using xDS.
This commit is contained in:
ZHANG Dapeng 2021-04-14 23:06:37 -07:00 committed by GitHub
parent d4fa0ecc07
commit d25ebaf57d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 28 additions and 0 deletions

View File

@ -1179,6 +1179,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
return delegate; return delegate;
} }
@SuppressWarnings("unchecked")
@Override @Override
public void start(Listener<RespT> observer, Metadata headers) { public void start(Listener<RespT> observer, Metadata headers) {
PickSubchannelArgs args = new PickSubchannelArgsImpl(method, headers, callOptions); PickSubchannelArgs args = new PickSubchannelArgsImpl(method, headers, callOptions);
@ -1186,6 +1187,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
Status status = result.getStatus(); Status status = result.getStatus();
if (!status.isOk()) { if (!status.isOk()) {
executeCloseObserverInContext(observer, status); executeCloseObserverInContext(observer, status);
delegate = (ClientCall<ReqT, RespT>) NOOP_CALL;
return; return;
} }
ClientInterceptor interceptor = result.getInterceptor(); ClientInterceptor interceptor = result.getInterceptor();
@ -1226,6 +1228,29 @@ final class ManagedChannelImpl extends ManagedChannel implements
} }
} }
private static final ClientCall<Object, Object> NOOP_CALL = new ClientCall<Object, Object>() {
@Override
public void start(Listener<Object> responseListener, Metadata headers) {}
@Override
public void request(int numMessages) {}
@Override
public void cancel(String message, Throwable cause) {}
@Override
public void halfClose() {}
@Override
public void sendMessage(Object message) {}
// Always returns {@code false}, since this is only used when the startup of the call fails.
@Override
public boolean isReady() {
return false;
}
};
/** /**
* Terminate the channel if termination conditions are met. * Terminate the channel if termination conditions are met.
*/ */

View File

@ -135,6 +135,9 @@ public class ConfigSelectingClientCallTest {
ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(null); ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(null);
verify(callListener).onClose(statusCaptor.capture(), any(Metadata.class)); verify(callListener).onClose(statusCaptor.capture(), any(Metadata.class));
assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.FAILED_PRECONDITION); assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.FAILED_PRECONDITION);
// The call should not delegate to null and fail methods with NPE.
configSelectingClientCall.request(1);
} }
private final class TestChannel extends Channel { private final class TestChannel extends Channel {