HTTP/2 RST_STREAM Regression f990f99
Motivation:
Commit f990f99
introduced a bug into the RST_STREAM processing that would prevent a RST_STREAM from being sent when it should have been. The promise would be marked as successful even though the RST_STREAM frame would never be sent.
Modifications:
- Fix conditional in Http2ConnectionHandler.resetStream to allow reset streams to be sent in all stream states besides IDLE.
Result:
RST_STREAM frames are now sent when they are supposed to be sent
Fixes https://github.com/netty/netty/issues/4856
This commit is contained in:
parent
36aa11937d
commit
56e6e07b25
@ -617,9 +617,8 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
||||
}
|
||||
|
||||
final ChannelFuture future;
|
||||
if (stream.state() == IDLE || connection().local().created(stream)) {
|
||||
// The other endpoint doesn't know about the stream yet, so we can't actually send
|
||||
// the RST_STREAM frame. The HTTP/2 spec also disallows sending RST_STREAM for IDLE streams.
|
||||
if (stream.state() == IDLE) {
|
||||
// We cannot write RST_STREAM frames on IDLE streams https://tools.ietf.org/html/rfc7540#section-6.4.
|
||||
future = promise.setSuccess();
|
||||
} else {
|
||||
future = frameWriter().writeRstStream(ctx, streamId, errorCode, promise);
|
||||
@ -629,17 +628,16 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
||||
// from resulting in multiple reset frames being sent.
|
||||
stream.resetSent();
|
||||
|
||||
future.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
closeStream(stream, promise);
|
||||
} else {
|
||||
// The connection will be closed and so no need to change the resetSent flag to false.
|
||||
onConnectionError(ctx, future.cause(), null);
|
||||
if (future.isDone()) {
|
||||
processRstStreamWriteResult(ctx, stream, future);
|
||||
} else {
|
||||
future.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
processRstStreamWriteResult(ctx, stream, future);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
@ -688,10 +686,10 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
||||
// If this connection is closing and the graceful shutdown has completed, close the connection
|
||||
// once this operation completes.
|
||||
if (closeListener != null && isGracefulShutdownComplete()) {
|
||||
ChannelFutureListener closeListener = Http2ConnectionHandler.this.closeListener;
|
||||
ChannelFutureListener closeListener = this.closeListener;
|
||||
// This method could be called multiple times
|
||||
// and we don't want to notify the closeListener multiple times.
|
||||
Http2ConnectionHandler.this.closeListener = null;
|
||||
this.closeListener = null;
|
||||
try {
|
||||
closeListener.operationComplete(future);
|
||||
} catch (Exception e) {
|
||||
@ -717,6 +715,15 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
|
||||
return goAway(ctx, lastKnownStream, errorCode, debugData, ctx.newPromise());
|
||||
}
|
||||
|
||||
private void processRstStreamWriteResult(ChannelHandlerContext ctx, Http2Stream stream, ChannelFuture future) {
|
||||
if (future.isSuccess()) {
|
||||
closeStream(stream, future);
|
||||
} else {
|
||||
// The connection will be closed and so no need to change the resetSent flag to false.
|
||||
onConnectionError(ctx, future.cause(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client preface string if this is a client connection, otherwise returns {@code null}.
|
||||
*/
|
||||
|
@ -43,6 +43,7 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf;
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.IDLE;
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@ -321,6 +322,15 @@ public class Http2ConnectionHandlerTest {
|
||||
verify(frameWriter).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(ChannelPromise.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeRstOnIdleStreamShouldNotWriteButStillSucceed() throws Exception {
|
||||
handler = newHandler();
|
||||
when(stream.state()).thenReturn(IDLE);
|
||||
handler.resetStream(ctx, STREAM_ID, STREAM_CLOSED.code(), promise);
|
||||
verify(frameWriter, never()).writeRstStream(eq(ctx), eq(STREAM_ID), anyLong(), any(ChannelPromise.class));
|
||||
verify(stream).close();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void closeListenerShouldBeNotifiedOnlyOneTime() throws Exception {
|
||||
|
Loading…
Reference in New Issue
Block a user