Http2FrameCodec to simulate GOAWAY received when stream IDs are exhausted (#9095)
Motivation: Http2FrameCodec currently fails the write promise associated with creating a stream with a Http2NoMoreStreamIdsException. However this means the user code will have to listen to all write futures in order to catch this scenario which is the same as receiving a GOAWAY frame. We can also simulate receiving a GOAWAY frame from our remote peer and that allows users to consolidate graceful close logic in the GOAWAY processing. Modifications: - Http2FrameCodec should simulate a DefaultHttp2GoAwayFrame when trying to create a stream but the stream IDs have been exhausted. Result: Applications can rely upon GOAWAY for graceful close processing instead of also processing write futures.
This commit is contained in:
parent
ec62af01c7
commit
2c12f09ec9
@ -35,8 +35,10 @@ import io.netty.util.internal.UnstableApi;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import static io.netty.buffer.ByteBufUtil.writeAscii;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.isStreamIdValid;
|
||||
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
|
||||
|
||||
/**
|
||||
* <p><em>This API is very immature.</em> The Http2Connection-based API is currently preferred over this API.
|
||||
@ -361,6 +363,12 @@ public class Http2FrameCodec extends Http2ConnectionHandler {
|
||||
final int streamId = connection.local().incrementAndGetNextStreamId();
|
||||
if (streamId < 0) {
|
||||
promise.setFailure(new Http2NoMoreStreamIdsException());
|
||||
|
||||
// Simulate a GOAWAY being received due to stream exhaustion on this connection. We use the maximum
|
||||
// valid stream ID for the current peer.
|
||||
onHttp2Frame(ctx, new DefaultHttp2GoAwayFrame(connection.isServer() ? Integer.MAX_VALUE :
|
||||
Integer.MAX_VALUE - 1, NO_ERROR.code(),
|
||||
writeAscii(ctx.alloc(), "Stream IDs exhausted on local stream creation")));
|
||||
return;
|
||||
}
|
||||
stream.id = streamId;
|
||||
|
@ -55,6 +55,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.isStreamIdValid;
|
||||
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.anyChannelPromise;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.anyHttp2Settings;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.assertEqualsAndRelease;
|
||||
@ -303,9 +304,9 @@ public class Http2FrameCodecTest {
|
||||
Http2HeadersFrame actualHeaders = inboundHandler.readInbound();
|
||||
assertEquals(expectedHeaders.stream(actualHeaders.stream()), actualHeaders);
|
||||
|
||||
frameInboundWriter.writeInboundRstStream(3, Http2Error.NO_ERROR.code());
|
||||
frameInboundWriter.writeInboundRstStream(3, NO_ERROR.code());
|
||||
|
||||
Http2ResetFrame expectedRst = new DefaultHttp2ResetFrame(Http2Error.NO_ERROR).stream(actualHeaders.stream());
|
||||
Http2ResetFrame expectedRst = new DefaultHttp2ResetFrame(NO_ERROR).stream(actualHeaders.stream());
|
||||
Http2ResetFrame actualRst = inboundHandler.readInbound();
|
||||
assertEquals(expectedRst, actualRst);
|
||||
|
||||
@ -322,12 +323,12 @@ public class Http2FrameCodecTest {
|
||||
ByteBuf debugData = bb("debug");
|
||||
ByteBuf expected = debugData.copy();
|
||||
|
||||
Http2GoAwayFrame goAwayFrame = new DefaultHttp2GoAwayFrame(Http2Error.NO_ERROR.code(), debugData);
|
||||
Http2GoAwayFrame goAwayFrame = new DefaultHttp2GoAwayFrame(NO_ERROR.code(), debugData);
|
||||
goAwayFrame.setExtraStreamIds(2);
|
||||
|
||||
channel.writeOutbound(goAwayFrame);
|
||||
verify(frameWriter).writeGoAway(eqFrameCodecCtx(), eq(7),
|
||||
eq(Http2Error.NO_ERROR.code()), eq(expected), anyChannelPromise());
|
||||
eq(NO_ERROR.code()), eq(expected), anyChannelPromise());
|
||||
assertEquals(1, debugData.refCnt());
|
||||
assertEquals(State.OPEN, stream.state());
|
||||
assertTrue(channel.isActive());
|
||||
@ -338,8 +339,8 @@ public class Http2FrameCodecTest {
|
||||
@Test
|
||||
public void receiveGoaway() throws Exception {
|
||||
ByteBuf debugData = bb("foo");
|
||||
frameInboundWriter.writeInboundGoAway(2, Http2Error.NO_ERROR.code(), debugData);
|
||||
Http2GoAwayFrame expectedFrame = new DefaultHttp2GoAwayFrame(2, Http2Error.NO_ERROR.code(), bb("foo"));
|
||||
frameInboundWriter.writeInboundGoAway(2, NO_ERROR.code(), debugData);
|
||||
Http2GoAwayFrame expectedFrame = new DefaultHttp2GoAwayFrame(2, NO_ERROR.code(), bb("foo"));
|
||||
Http2GoAwayFrame actualFrame = inboundHandler.readInbound();
|
||||
|
||||
assertEqualsAndRelease(expectedFrame, actualFrame);
|
||||
@ -385,13 +386,13 @@ public class Http2FrameCodecTest {
|
||||
assertEquals(State.OPEN, stream.state());
|
||||
|
||||
ByteBuf debugData = bb("debug");
|
||||
Http2GoAwayFrame goAwayFrame = new DefaultHttp2GoAwayFrame(Http2Error.NO_ERROR.code(), debugData.slice());
|
||||
Http2GoAwayFrame goAwayFrame = new DefaultHttp2GoAwayFrame(NO_ERROR.code(), debugData.slice());
|
||||
goAwayFrame.setExtraStreamIds(Integer.MAX_VALUE);
|
||||
|
||||
channel.writeOutbound(goAwayFrame);
|
||||
// When the last stream id computation overflows, the last stream id should just be set to 2^31 - 1.
|
||||
verify(frameWriter).writeGoAway(eqFrameCodecCtx(), eq(Integer.MAX_VALUE),
|
||||
eq(Http2Error.NO_ERROR.code()), eq(debugData), anyChannelPromise());
|
||||
eq(NO_ERROR.code()), eq(debugData), anyChannelPromise());
|
||||
assertEquals(1, debugData.refCnt());
|
||||
assertEquals(State.OPEN, stream.state());
|
||||
assertTrue(channel.isActive());
|
||||
@ -675,6 +676,11 @@ public class Http2FrameCodecTest {
|
||||
ChannelPromise writePromise = channel.newPromise();
|
||||
channel.writeAndFlush(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers()).stream(stream), writePromise);
|
||||
|
||||
Http2GoAwayFrame goAwayFrame = inboundHandler.readInbound();
|
||||
assertNotNull(goAwayFrame);
|
||||
assertEquals(NO_ERROR.code(), goAwayFrame.errorCode());
|
||||
assertEquals(Integer.MAX_VALUE, goAwayFrame.lastStreamId());
|
||||
goAwayFrame.release();
|
||||
assertThat(writePromise.cause(), instanceOf(Http2NoMoreStreamIdsException.class));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user