Skip to content

Commit 94ab6f3

Browse files
authored
Introduce Http2MultiplexActiveStreamsException that can be used to propagate an error to all active streams (#13306)
Motivation: There might be a desire to fire an exception to all the active streams. This is currently harder to do then it should be as the user needs to keep track of the active streams. Modifications: - Add Http2MultiplexActiveStreamsException which can be used to wrap an exception which should be fired to all the active streams by Http2MultiplexHandler - Add unit tests Result: Alternative solution for #12830. Thanks @yawkat
1 parent 12e1169 commit 94ab6f3

4 files changed

Lines changed: 117 additions & 10 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2023 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.handler.codec.http2;
17+
18+
/**
19+
* {@link Exception} that can be used to wrap some {@link Throwable} and fire it through the pipeline.
20+
* The {@link Http2MultiplexHandler} will unwrap the original {@link Throwable} and fire it to all its
21+
* active {@link Http2StreamChannel}.
22+
*/
23+
public final class Http2MultiplexActiveStreamsException extends Exception {
24+
25+
public Http2MultiplexActiveStreamsException(Throwable cause) {
26+
super(cause);
27+
}
28+
29+
@Override
30+
public Throwable fillInStackTrace() {
31+
return this;
32+
}
33+
}

codec-http2/src/main/java/io/netty/handler/codec/http2/Http2MultiplexHandler.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -280,20 +280,30 @@ public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) th
280280
}
281281
return;
282282
}
283+
if (cause instanceof Http2MultiplexActiveStreamsException) {
284+
// Unwrap the cause that was used to create it and fire it for all the active streams.
285+
fireExceptionCaughtForActiveStream(cause.getCause());
286+
return;
287+
}
288+
283289
if (cause.getCause() instanceof SSLException) {
284-
forEachActiveStream(new Http2FrameStreamVisitor() {
285-
@Override
286-
public boolean visit(Http2FrameStream stream) {
287-
AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
288-
((DefaultHttp2FrameStream) stream).attachment;
289-
childChannel.pipeline().fireExceptionCaught(cause);
290-
return true;
291-
}
292-
});
290+
fireExceptionCaughtForActiveStream(cause);
293291
}
294292
ctx.fireExceptionCaught(cause);
295293
}
296294

295+
private void fireExceptionCaughtForActiveStream(final Throwable cause) throws Http2Exception {
296+
forEachActiveStream(new Http2FrameStreamVisitor() {
297+
@Override
298+
public boolean visit(Http2FrameStream stream) {
299+
AbstractHttp2StreamChannel childChannel = (AbstractHttp2StreamChannel)
300+
((DefaultHttp2FrameStream) stream).attachment;
301+
childChannel.pipeline().fireExceptionCaught(cause);
302+
return true;
303+
}
304+
});
305+
}
306+
297307
private static boolean isServer(ChannelHandlerContext ctx) {
298308
return ctx.channel().parent() instanceof ServerChannel;
299309
}

codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexHandlerTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
package io.netty.handler.codec.http2;
1616

1717
import io.netty.channel.ChannelHandler;
18+
import io.netty.channel.ChannelHandlerContext;
19+
import io.netty.channel.ChannelInboundHandlerAdapter;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.function.Executable;
22+
23+
import javax.net.ssl.SSLException;
24+
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
import static org.junit.jupiter.api.Assertions.assertThrows;
27+
import static org.junit.jupiter.api.Assertions.assertTrue;
1828

1929
/**
2030
* Unit tests for {@link Http2MultiplexHandler}.
@@ -40,4 +50,58 @@ protected boolean useUserEventForResetFrame() {
4050
protected boolean ignoreWindowUpdateFrames() {
4151
return true;
4252
}
53+
54+
@Test
55+
public void sslExceptionTriggersChildChannelException() {
56+
final LastInboundHandler inboundHandler = new LastInboundHandler();
57+
Http2StreamChannel channel = newInboundStream(3, false, inboundHandler);
58+
assertTrue(channel.isActive());
59+
final RuntimeException testExc = new RuntimeException(new SSLException("foo"));
60+
channel.parent().pipeline().addLast(new ChannelInboundHandlerAdapter() {
61+
@Override
62+
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
63+
if (cause != testExc) {
64+
super.exceptionCaught(ctx, cause);
65+
}
66+
}
67+
});
68+
channel.parent().pipeline().fireExceptionCaught(testExc);
69+
70+
assertTrue(channel.isActive());
71+
RuntimeException exc = assertThrows(RuntimeException.class, new Executable() {
72+
@Override
73+
public void execute() throws Throwable {
74+
inboundHandler.checkException();
75+
}
76+
});
77+
assertEquals(testExc, exc);
78+
}
79+
80+
@Test
81+
public void customExceptionForwarding() {
82+
final LastInboundHandler inboundHandler = new LastInboundHandler();
83+
Http2StreamChannel channel = newInboundStream(3, false, inboundHandler);
84+
assertTrue(channel.isActive());
85+
final RuntimeException testExc = new RuntimeException("xyz");
86+
channel.parent().pipeline().addLast(new ChannelInboundHandlerAdapter() {
87+
@Override
88+
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
89+
if (cause != testExc) {
90+
super.exceptionCaught(ctx, cause);
91+
} else {
92+
ctx.pipeline().fireExceptionCaught(new Http2MultiplexActiveStreamsException(cause));
93+
}
94+
}
95+
});
96+
channel.parent().pipeline().fireExceptionCaught(testExc);
97+
98+
assertTrue(channel.isActive());
99+
RuntimeException exc = assertThrows(RuntimeException.class, new Executable() {
100+
@Override
101+
public void execute() throws Throwable {
102+
inboundHandler.checkException();
103+
}
104+
});
105+
assertEquals(testExc, exc);
106+
}
43107
}

codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public void channelActive(ChannelHandlerContext ctx) {
161161
any(ByteBuf.class), any(ChannelPromise.class));
162162
}
163163

164-
private Http2StreamChannel newInboundStream(int streamId, boolean endStream, final ChannelHandler childHandler) {
164+
Http2StreamChannel newInboundStream(int streamId, boolean endStream, final ChannelHandler childHandler) {
165165
return newInboundStream(streamId, endStream, null, childHandler);
166166
}
167167

0 commit comments

Comments
 (0)