diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 27b2c74be2d..b3ebf2367e7 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -58,6 +58,7 @@ import io.netty.util.AttributeMap; import java.net.SocketAddress; import java.net.URI; +import java.nio.channels.ClosedChannelException; import java.util.Arrays; import java.util.concurrent.Executor; import java.util.logging.Level; @@ -372,7 +373,18 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws ctx.fireExceptionCaught(ex); } } else { - ctx.fireExceptionCaught(handshakeEvent.cause()); + Throwable t = handshakeEvent.cause(); + if (t instanceof ClosedChannelException) { + // On channelInactive(), SslHandler creates its own ClosedChannelException and + // propagates it before the actual channelInactive(). So we assume here that any + // such exception is from channelInactive() and emulate the normal behavior of + // WriteBufferingAndExceptionHandler + t = Status.UNAVAILABLE + .withDescription("Connection closed while performing TLS negotiation") + .withCause(t) + .asRuntimeException(); + } + ctx.fireExceptionCaught(t); } } else { super.userEventTriggered0(ctx, evt); diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 454632715a4..d83a5fabaf5 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -33,6 +33,8 @@ import io.grpc.Grpc; import io.grpc.InternalChannelz.Security; import io.grpc.SecurityLevel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import io.grpc.internal.GrpcAttributes; import io.grpc.internal.testing.TestUtils; import io.grpc.netty.ProtocolNegotiators.ClientTlsHandler; @@ -534,6 +536,20 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { assertNull(grpcHandlerCtx); } + @Test + public void clientTlsHandler_closeDuringNegotiation() throws Exception { + ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, "authority", null); + pipeline.addLast(new WriteBufferingAndExceptionHandler(handler)); + ChannelFuture pendingWrite = channel.writeAndFlush(NettyClientHandler.NOOP_MESSAGE); + + // SslHandler fires userEventTriggered() before channelInactive() + pipeline.fireChannelInactive(); + + assertThat(pendingWrite.cause()).isInstanceOf(StatusRuntimeException.class); + assertThat(Status.fromThrowable(pendingWrite.cause()).getCode()) + .isEqualTo(Status.Code.UNAVAILABLE); + } + @Test public void engineLog() { ChannelHandler handler = new ServerTlsHandler(grpcHandler, sslContext, null);