Skip to content

Commit bded2a1

Browse files
authored
HTTP2: Always apply the graceful shutdown timeout if configured (netty#9340)
Motivation: Http2ConnectionHandler (and sub-classes) allow to configure a graceful shutdown timeout but only apply it if there is at least one active stream. We should always apply the timeout. This is also true when we try to send a GO_AWAY and close the connection because of an connection error. Modifications: - Always apply the timeout if one is configured - Add unit test Result: Always respect gracefulShutdownTimeoutMillis
1 parent 4312e11 commit bded2a1

File tree

2 files changed

+46
-8
lines changed

2 files changed

+46
-8
lines changed

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -477,19 +477,27 @@ public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exce
477477
doGracefulShutdown(ctx, f, promise);
478478
}
479479

480+
private ChannelFutureListener newClosingChannelFutureListener(
481+
ChannelHandlerContext ctx, ChannelPromise promise) {
482+
long gracefulShutdownTimeoutMillis = this.gracefulShutdownTimeoutMillis;
483+
return gracefulShutdownTimeoutMillis < 0 ?
484+
new ClosingChannelFutureListener(ctx, promise) :
485+
new ClosingChannelFutureListener(ctx, promise, gracefulShutdownTimeoutMillis, MILLISECONDS);
486+
}
487+
480488
private void doGracefulShutdown(ChannelHandlerContext ctx, ChannelFuture future, final ChannelPromise promise) {
489+
final ChannelFutureListener listener = newClosingChannelFutureListener(ctx, promise);
481490
if (isGracefulShutdownComplete()) {
482-
// If there are no active streams, close immediately after the GO_AWAY write completes.
483-
future.addListener(new ClosingChannelFutureListener(ctx, promise));
491+
// If there are no active streams, close immediately after the GO_AWAY write completes or the timeout
492+
// elapsed.
493+
future.addListener(listener);
484494
} else {
485495
// If there are active streams we should wait until they are all closed before closing the connection.
486-
final ClosingChannelFutureListener tmp = gracefulShutdownTimeoutMillis < 0 ?
487-
new ClosingChannelFutureListener(ctx, promise) :
488-
new ClosingChannelFutureListener(ctx, promise, gracefulShutdownTimeoutMillis, MILLISECONDS);
496+
489497
// The ClosingChannelFutureListener will cascade promise completion. We need to always notify the
490498
// new ClosingChannelFutureListener when the graceful close completes if the promise is not null.
491499
if (closeListener == null) {
492-
closeListener = tmp;
500+
closeListener = listener;
493501
} else if (promise != null) {
494502
final ChannelFutureListener oldCloseListener = closeListener;
495503
closeListener = new ChannelFutureListener() {
@@ -498,7 +506,7 @@ public void operationComplete(ChannelFuture future) throws Exception {
498506
try {
499507
oldCloseListener.operationComplete(future);
500508
} finally {
501-
tmp.operationComplete(future);
509+
listener.operationComplete(future);
502510
}
503511
}
504512
};
@@ -665,7 +673,7 @@ protected void onConnectionError(ChannelHandlerContext ctx, boolean outbound,
665673
if (http2Ex.shutdownHint() == Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN) {
666674
doGracefulShutdown(ctx, future, promise);
667675
} else {
668-
future.addListener(new ClosingChannelFutureListener(ctx, promise));
676+
future.addListener(newClosingChannelFutureListener(ctx, promise));
669677
}
670678
}
671679

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import io.netty.channel.DefaultChannelPromise;
3030
import io.netty.handler.codec.http.HttpResponseStatus;
3131
import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator;
32+
import io.netty.handler.codec.http2.Http2Exception.ShutdownHint;
3233
import io.netty.util.ReferenceCountUtil;
3334
import io.netty.util.concurrent.EventExecutor;
3435
import io.netty.util.concurrent.GenericFutureListener;
@@ -721,6 +722,25 @@ public void writeRstStreamForKnownStreamUsingVoidPromise() throws Exception {
721722
writeRstStreamUsingVoidPromise(STREAM_ID);
722723
}
723724

725+
@Test
726+
public void gracefulShutdownTimeoutWhenConnectionErrorHardShutdownTest() throws Exception {
727+
gracefulShutdownTimeoutWhenConnectionErrorTest0(ShutdownHint.HARD_SHUTDOWN);
728+
}
729+
730+
@Test
731+
public void gracefulShutdownTimeoutWhenConnectionErrorGracefulShutdownTest() throws Exception {
732+
gracefulShutdownTimeoutWhenConnectionErrorTest0(ShutdownHint.GRACEFUL_SHUTDOWN);
733+
}
734+
735+
private void gracefulShutdownTimeoutWhenConnectionErrorTest0(ShutdownHint hint) throws Exception {
736+
handler = newHandler();
737+
final long expectedMillis = 1234;
738+
handler.gracefulShutdownTimeoutMillis(expectedMillis);
739+
Http2Exception exception = new Http2Exception(PROTOCOL_ERROR, "Test error", hint);
740+
handler.onConnectionError(ctx, false, exception, exception);
741+
verify(executor, atLeastOnce()).schedule(any(Runnable.class), eq(expectedMillis), eq(TimeUnit.MILLISECONDS));
742+
}
743+
724744
@Test
725745
public void gracefulShutdownTimeoutTest() throws Exception {
726746
handler = newHandler();
@@ -730,6 +750,16 @@ public void gracefulShutdownTimeoutTest() throws Exception {
730750
verify(executor, atLeastOnce()).schedule(any(Runnable.class), eq(expectedMillis), eq(TimeUnit.MILLISECONDS));
731751
}
732752

753+
@Test
754+
public void gracefulShutdownTimeoutNoActiveStreams() throws Exception {
755+
handler = newHandler();
756+
when(connection.numActiveStreams()).thenReturn(0);
757+
final long expectedMillis = 1234;
758+
handler.gracefulShutdownTimeoutMillis(expectedMillis);
759+
handler.close(ctx, promise);
760+
verify(executor, atLeastOnce()).schedule(any(Runnable.class), eq(expectedMillis), eq(TimeUnit.MILLISECONDS));
761+
}
762+
733763
@Test
734764
public void gracefulShutdownIndefiniteTimeoutTest() throws Exception {
735765
handler = newHandler();

0 commit comments

Comments
 (0)