Skip to content

Commit c1b9631

Browse files
authored
When in graceful shutdown, send GO_AWAY to notify the client (#2758)
Configure Http2FrameCodec with indefinite graceful shutdown timeout as the scheduling happens with ServerTransport#disposeNow https://datatracker.ietf.org/doc/html/rfc9113#GOAWAY "The GOAWAY frame (type=0x07) is used to initiate shutdown of a connection... GOAWAY allows an endpoint to gracefully stop accepting new streams while still finishing processing of previously established streams." "Once the GOAWAY is sent, the sender will ignore frames sent on streams initiated by the receiver if the stream has an identifier higher than the included last stream identifier. Receivers of a GOAWAY frame MUST NOT open additional streams on the connection..." "A GOAWAY frame might not immediately precede closing of the connection" "Activity on streams numbered lower than or equal to the last stream identifier might still complete successfully. The sender of a GOAWAY frame might gracefully shut down a connection by sending a GOAWAY frame, maintaining the connection in an "open" state until all in-progress streams complete." Fix #2735
1 parent a9fe467 commit c1b9631

File tree

2 files changed

+46
-13
lines changed

2 files changed

+46
-13
lines changed

reactor-netty-core/src/main/java/reactor/netty/transport/ServerTransport.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import reactor.netty.Connection;
5151
import reactor.netty.ConnectionObserver;
5252
import reactor.netty.DisposableServer;
53+
import reactor.netty.FutureMono;
5354
import reactor.netty.channel.AbortedException;
5455
import reactor.netty.channel.ChannelOperations;
5556
import reactor.netty.internal.util.MapUtils;
@@ -530,12 +531,15 @@ public final void dispose() {
530531
@Override
531532
@SuppressWarnings("FutureReturnValueIgnored")
532533
public void disposeNow(Duration timeout) {
533-
if (log.isDebugEnabled()) {
534-
log.debug(format(channel(), "Server is about to be disposed with timeout: {}"), timeout);
535-
}
536534
if (isDisposed()) {
535+
if (log.isDebugEnabled()) {
536+
log.debug(format(channel(), "Server has been disposed"));
537+
}
537538
return;
538539
}
540+
if (log.isDebugEnabled()) {
541+
log.debug(format(channel(), "Server is about to be disposed with timeout: {}"), timeout);
542+
}
539543
dispose();
540544
Mono<Void> terminateSignals = Mono.empty();
541545
if (config.channelGroup != null && config.channelGroup.size() > 0) {
@@ -544,10 +548,21 @@ public void disposeNow(Duration timeout) {
544548
// Wait for the running requests to finish
545549
for (Channel channel : config.channelGroup) {
546550
Channel parent = channel.parent();
551+
// For TCP and HTTP/1.1 the channel parent is the ServerChannel
552+
boolean isParentServerChannel = parent instanceof ServerChannel;
547553
List<Mono<Void>> monos =
548554
MapUtils.computeIfAbsent(channelsToMono,
549-
parent instanceof ServerChannel ? channel : parent,
550-
key -> new ArrayList<>());
555+
isParentServerChannel ? channel : parent,
556+
key -> {
557+
List<Mono<Void>> list = new ArrayList<>();
558+
if (!isParentServerChannel) {
559+
// In case of HTTP/2 Reactor Netty will send GO_AWAY with lastStreamId to notify the
560+
// client to stop opening streams, the actual CLOSE will happen when all
561+
// streams up to lastStreamId are closed
562+
list.add(FutureMono.from(key.close()));
563+
}
564+
return list;
565+
});
551566
ChannelOperations<?, ?> ops = ChannelOperations.get(channel);
552567
if (ops != null) {
553568
monos.add(ops.onTerminate().doFinally(sig -> ops.dispose()));
@@ -558,16 +573,12 @@ public void disposeNow(Duration timeout) {
558573
List<Mono<Void>> monos = entry.getValue();
559574
if (monos.isEmpty()) {
560575
// At this point there are no running requests for this channel
576+
// This can happen for TCP and HTTP/1.1
561577
// "FutureReturnValueIgnored" this is deliberate
562578
channel.close();
563579
}
564580
else {
565-
terminateSignals =
566-
Mono.when(monos)
567-
// At this point there are no running requests for this channel
568-
// "FutureReturnValueIgnored" this is deliberate
569-
.doFinally(sig -> channel.close())
570-
.and(terminateSignals);
581+
terminateSignals = Mono.when(monos).and(terminateSignals);
571582
}
572583
}
573584
}

reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServerConfig.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ static void configureH2Pipeline(ChannelPipeline p,
503503
@Nullable BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate,
504504
ServerCookieDecoder cookieDecoder,
505505
ServerCookieEncoder cookieEncoder,
506+
boolean enableGracefulShutdown,
506507
HttpServerFormDecoderProvider formDecoderProvider,
507508
@Nullable BiFunction<ConnectionInfo, HttpRequest, ConnectionInfo> forwardedHeaderHandler,
508509
Http2Settings http2Settings,
@@ -522,6 +523,12 @@ static void configureH2Pipeline(ChannelPipeline p,
522523
.validateHeaders(validate)
523524
.initialSettings(http2Settings);
524525

526+
if (enableGracefulShutdown) {
527+
// Configure the graceful shutdown with indefinite timeout as Reactor Netty controls the timeout
528+
// when disposeNow(timeout) is invoked
529+
http2FrameCodecBuilder.gracefulShutdownTimeoutMillis(-1);
530+
}
531+
525532
if (p.get(NettyPipeline.LoggingHandler) != null) {
526533
http2FrameCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG,
527534
"reactor.netty.http.server.h2"));
@@ -552,6 +559,7 @@ static void configureHttp11OrH2CleartextPipeline(ChannelPipeline p,
552559
ServerCookieDecoder cookieDecoder,
553560
ServerCookieEncoder cookieEncoder,
554561
HttpRequestDecoderSpec decoder,
562+
boolean enableGracefulShutdown,
555563
HttpServerFormDecoderProvider formDecoderProvider,
556564
@Nullable BiFunction<ConnectionInfo, HttpRequest, ConnectionInfo> forwardedHeaderHandler,
557565
Http2Settings http2Settings,
@@ -570,7 +578,7 @@ static void configureHttp11OrH2CleartextPipeline(ChannelPipeline p,
570578
decoder.allowDuplicateContentLengths());
571579

572580
Http11OrH2CleartextCodec upgrader = new Http11OrH2CleartextCodec(accessLogEnabled, accessLog, compressPredicate,
573-
cookieDecoder, cookieEncoder, p.get(NettyPipeline.LoggingHandler) != null, formDecoderProvider,
581+
cookieDecoder, cookieEncoder, p.get(NettyPipeline.LoggingHandler) != null, enableGracefulShutdown, formDecoderProvider,
574582
forwardedHeaderHandler, http2Settings, httpMessageLogFactory, listener, mapHandle, metricsRecorder,
575583
minCompressionSize, opsFactory, uriTagValue, decoder.validateHeaders());
576584

@@ -880,6 +888,7 @@ static final class Http11OrH2CleartextCodec extends ChannelInitializer<Channel>
880888
ServerCookieDecoder cookieDecoder,
881889
ServerCookieEncoder cookieEncoder,
882890
boolean debug,
891+
boolean enableGracefulShutdown,
883892
HttpServerFormDecoderProvider formDecoderProvider,
884893
@Nullable BiFunction<ConnectionInfo, HttpRequest, ConnectionInfo> forwardedHeaderHandler,
885894
Http2Settings http2Settings,
@@ -903,6 +912,12 @@ static final class Http11OrH2CleartextCodec extends ChannelInitializer<Channel>
903912
.validateHeaders(validate)
904913
.initialSettings(http2Settings);
905914

915+
if (enableGracefulShutdown) {
916+
// Configure the graceful shutdown with indefinite timeout as Reactor Netty controls the timeout
917+
// when disposeNow(timeout) is invoked
918+
http2FrameCodecBuilder.gracefulShutdownTimeoutMillis(-1);
919+
}
920+
906921
if (debug) {
907922
http2FrameCodecBuilder.frameLogger(new Http2FrameLogger(
908923
LogLevel.DEBUG,
@@ -949,6 +964,7 @@ static final class H2OrHttp11Codec extends ApplicationProtocolNegotiationHandler
949964
final ServerCookieDecoder cookieDecoder;
950965
final ServerCookieEncoder cookieEncoder;
951966
final HttpRequestDecoderSpec decoder;
967+
final boolean enableGracefulShutdown;
952968
final HttpServerFormDecoderProvider formDecoderProvider;
953969
final BiFunction<ConnectionInfo, HttpRequest, ConnectionInfo> forwardedHeaderHandler;
954970
final Http2Settings http2Settings;
@@ -971,6 +987,7 @@ static final class H2OrHttp11Codec extends ApplicationProtocolNegotiationHandler
971987
this.cookieDecoder = initializer.cookieDecoder;
972988
this.cookieEncoder = initializer.cookieEncoder;
973989
this.decoder = initializer.decoder;
990+
this.enableGracefulShutdown = initializer.enableGracefulShutdown;
974991
this.formDecoderProvider = initializer.formDecoderProvider;
975992
this.forwardedHeaderHandler = initializer.forwardedHeaderHandler;
976993
this.http2Settings = initializer.http2Settings;
@@ -995,7 +1012,7 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
9951012

9961013
if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
9971014
configureH2Pipeline(p, accessLogEnabled, accessLog, compressPredicate, cookieDecoder, cookieEncoder,
998-
formDecoderProvider, forwardedHeaderHandler, http2Settings, httpMessageLogFactory, idleTimeout,
1015+
enableGracefulShutdown, formDecoderProvider, forwardedHeaderHandler, http2Settings, httpMessageLogFactory, idleTimeout,
9991016
listener, mapHandle, metricsRecorder, minCompressionSize, opsFactory, uriTagValue, decoder.validateHeaders());
10001017
return;
10011018
}
@@ -1024,6 +1041,7 @@ static final class HttpServerChannelInitializer implements ChannelPipelineConfig
10241041
final ServerCookieDecoder cookieDecoder;
10251042
final ServerCookieEncoder cookieEncoder;
10261043
final HttpRequestDecoderSpec decoder;
1044+
final boolean enableGracefulShutdown;
10271045
final HttpServerFormDecoderProvider formDecoderProvider;
10281046
final BiFunction<ConnectionInfo, HttpRequest, ConnectionInfo> forwardedHeaderHandler;
10291047
final Http2Settings http2Settings;
@@ -1048,6 +1066,7 @@ static final class HttpServerChannelInitializer implements ChannelPipelineConfig
10481066
this.cookieDecoder = config.cookieDecoder;
10491067
this.cookieEncoder = config.cookieEncoder;
10501068
this.decoder = config.decoder;
1069+
this.enableGracefulShutdown = config.channelGroup() != null;
10511070
this.formDecoderProvider = config.formDecoderProvider;
10521071
this.forwardedHeaderHandler = config.forwardedHeaderHandler;
10531072
this.http2Settings = config.http2Settings();
@@ -1115,6 +1134,7 @@ else if ((protocols & h2) == h2) {
11151134
compressPredicate(compressPredicate, minCompressionSize),
11161135
cookieDecoder,
11171136
cookieEncoder,
1137+
enableGracefulShutdown,
11181138
formDecoderProvider,
11191139
forwardedHeaderHandler,
11201140
http2Settings,
@@ -1139,6 +1159,7 @@ else if ((protocols & h2) == h2) {
11391159
cookieDecoder,
11401160
cookieEncoder,
11411161
decoder,
1162+
enableGracefulShutdown,
11421163
formDecoderProvider,
11431164
forwardedHeaderHandler,
11441165
http2Settings,
@@ -1180,6 +1201,7 @@ else if ((protocols & h2c) == h2c) {
11801201
compressPredicate(compressPredicate, minCompressionSize),
11811202
cookieDecoder,
11821203
cookieEncoder,
1204+
enableGracefulShutdown,
11831205
formDecoderProvider,
11841206
forwardedHeaderHandler,
11851207
http2Settings,

0 commit comments

Comments
 (0)