diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index d59b504c709..83ce18e747f 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -47,6 +47,7 @@ import io.netty.util.concurrent.PromiseNotifier; import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.ThrowableUtil; import io.netty.util.internal.UnstableApi; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -1062,7 +1063,15 @@ private SSLEngineResult wrap(ByteBufAllocator alloc, SSLEngine engine, ByteBuf i public void channelInactive(ChannelHandlerContext ctx) throws Exception { boolean handshakeFailed = handshakePromise.cause() != null; + // Channel closed, we will generate 'ClosedChannelException' now. ClosedChannelException exception = new ClosedChannelException(); + + // Add a supressed exception if the handshake was not completed yet. + if (isStateSet(STATE_HANDSHAKE_STARTED) && !handshakePromise.isDone()) { + ThrowableUtil.addSuppressed(exception, + new StacklessSSLHandshakeException("Connection closed while SSL/TLS handshake was in progress")); + } + // Make sure to release SSLEngine, // and notify the handshake future if the connection has been closed during handshake. setHandshakeFailure(ctx, exception, !isStateSet(STATE_OUTBOUND_CLOSED), isStateSet(STATE_HANDSHAKE_STARTED), diff --git a/handler/src/main/java/io/netty/handler/ssl/StacklessSSLHandshakeException.java b/handler/src/main/java/io/netty/handler/ssl/StacklessSSLHandshakeException.java new file mode 100644 index 00000000000..3c315892fea --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/StacklessSSLHandshakeException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.ssl; + +import javax.net.ssl.SSLHandshakeException; + +/** + * A {@link SSLHandshakeException} that does not fill in the stack trace. + */ +final class StacklessSSLHandshakeException extends SSLHandshakeException { + + private static final long serialVersionUID = -1244781947804415549L; + + /** + * Constructs an exception reporting an error found by + * an SSL subsystem during handshaking. + * + * @param reason describes the problem. + */ + StacklessSSLHandshakeException(String reason) { + super(reason); + } + + @Override + public Throwable fillInStackTrace() { + // This is a performance optimization to not fill in the + // stack trace as this is a stackless exception. + return this; + } +} diff --git a/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java b/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java index 70aef23b602..bd8e0ac79e5 100644 --- a/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java +++ b/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java @@ -568,8 +568,8 @@ public void testCloseFutureNotified() throws Exception { assertFalse(ch.finishAndReleaseAll()); - assertTrue(handler.handshakeFuture().cause() instanceof ClosedChannelException); - assertTrue(handler.sslCloseFuture().cause() instanceof ClosedChannelException); + assertThat(handler.handshakeFuture().cause(), instanceOf(ClosedChannelException.class)); + assertThat(handler.sslCloseFuture().cause(), instanceOf(ClosedChannelException.class)); } @Test @@ -590,11 +590,11 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc SslCompletionEvent evt = events.take(); assertTrue(evt instanceof SslHandshakeCompletionEvent); - assertTrue(evt.cause() instanceof ClosedChannelException); + assertThat(evt.cause(), instanceOf(ClosedChannelException.class)); evt = events.take(); assertTrue(evt instanceof SslCloseCompletionEvent); - assertTrue(evt.cause() instanceof ClosedChannelException); + assertThat(evt.cause(), instanceOf(ClosedChannelException.class)); assertTrue(events.isEmpty()); }