core: Do not call startDeadlineTimer when is deadlineCancellationExecutor is null

We got a NullPointerException from ClientCallImpl#startDeadlineTimer
when a new Call is created after a Netty channel is terminated. Here
is a stacktrace:

INTERNAL: java.lang.NullPointerException
at io.grpc.internal.ClientCallImpl.startDeadlineTimer(ClientCallImpl.java:320)
at io.grpc.internal.ClientCallImpl.start(ClientCallImpl.java:253)

The following code snippet reproduces the bug:

```
ManagedChannel channel = NettyChannelBuilder.forAddress(host, port)
    .usePlaintext(true)
    .build();
channel.shutdown();

Thread.sleep(1000);

GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel)
    .withDeadlineAfter(10, TimeUnit.SECONDS);
stub.sayHello(HelloRequest.newBuilder().setName("world").build());
```

The issue was that ClientCallImpl is created from RealChannel#newCall
*after* ManagedChannelImpl#maybeTerminateChannel is called and
scheduledExecutor is set to null. In such a scenario,
deadlineCancellationExecutor is set to null.

I think there are several ways to fix this, but one way would be to
just avoid calling startDeadlineTimer() when
deadlineCancellationExecutor is null. DelayedClientTransport will
create a FailingClientStream with Status.UNAVAILABLE and we will get

```
Exception in thread "main" io.grpc.StatusRuntimeException:
UNAVAILABLE: Channel has shutdown (reported by delayed transport)
```
This commit is contained in:
Kenji Kaneda 2017-03-27 16:20:56 -07:00 committed by Eric Anderson
parent 6765596bb9
commit c131d2dd14
1 changed files with 3 additions and 1 deletions

View File

@ -249,7 +249,9 @@ final class ClientCallImpl<ReqT, RespT> extends ClientCall<ReqT, RespT>
context.addListener(this, directExecutor());
if (effectiveDeadline != null
// If the context has the effective deadline, we don't need to schedule an extra task.
&& context.getDeadline() != effectiveDeadline) {
&& context.getDeadline() != effectiveDeadline
// If the channel has been terminated, we don't need to schedule an extra task.
&& deadlineCancellationExecutor != null) {
deadlineCancellationFuture = startDeadlineTimer(effectiveDeadline);
}
if (cancelListenersShouldBeRemoved) {