From c131d2dd147136caecf326ffa7ff007e9ffd0771 Mon Sep 17 00:00:00 2001 From: Kenji Kaneda Date: Mon, 27 Mar 2017 16:20:56 -0700 Subject: [PATCH] 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) ``` --- core/src/main/java/io/grpc/internal/ClientCallImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java index 76df05fcf1..6d9eb20007 100644 --- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java +++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java @@ -249,7 +249,9 @@ final class ClientCallImpl extends ClientCall 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) {