Skip to content

Commit f743dc8

Browse files
committed
Improve graceful shutdown documentation to remove ambiguity
Closes gh-40108
1 parent f5f02d6 commit f743dc8

File tree

6 files changed

+75
-5
lines changed

6 files changed

+75
-5
lines changed

spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and servlet-based web applications.
44
It occurs as part of closing the application context and is performed in the earliest phase of stopping `SmartLifecycle` beans.
55
This stop processing uses a timeout which provides a grace period during which existing requests will be allowed to complete but no new requests will be permitted.
6+
67
The exact way in which new requests are not permitted varies depending on the web server that is being used.
7-
Jetty, Reactor Netty, and Tomcat will stop accepting requests at the network layer.
8-
Undertow will accept requests but respond immediately with a service unavailable (503) response.
8+
Implementations may stop accepting requests at the network layer, or they may return a response with a specific HTTP status code or HTTP header.
9+
The use of persistent connections can also change the way that requests stop being accepted.
10+
11+
TIP: To learn about more the specific method used with your web server, see the `shutDownGracefully` javadoc for {spring-boot-module-api}/web/embedded/tomcat/TomcatWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[TomcatWebServer], {spring-boot-module-api}/web/embedded/netty/NettyWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[NettyWebServer], {spring-boot-module-api}/web/embedded/jetty/JettyWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[JettyWebServer] or {spring-boot-module-api}/web/embedded/undertow/UndertowWebServer.html#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[UndertowWebServer].
12+
13+
Jetty, Reactor Netty, and Tomcat will stop accepting new requests at the network layer.
14+
Undertow will accept new connections but respond immediately with a service unavailable (503) response.
915

1016
NOTE: Graceful shutdown with Tomcat requires Tomcat 9.0.33 or later.
1117

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -268,6 +268,15 @@ private Integer getLocalPort(Connector connector) {
268268
return 0;
269269
}
270270

271+
/**
272+
* Initiates a graceful shutdown of the Jetty web server. Handling of new requests is
273+
* prevented and the given {@code callback} is invoked at the end of the attempt. The
274+
* attempt can be explicitly ended by invoking {@link #stop}.
275+
* <p>
276+
* Once shutdown has been initiated Jetty will reject any new connections. Requests on
277+
* existing connections will be accepted, however, a {@code Connection: close} header
278+
* will be returned in the response.
279+
*/
271280
@Override
272281
public void shutDownGracefully(GracefulShutdownCallback callback) {
273282
if (this.gracefulShutdown == null) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java

+8
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ private boolean isPermissionDenied(Throwable bindExceptionCause) {
157157
return false;
158158
}
159159

160+
/**
161+
* Initiates a graceful shutdown of the Netty web server. Handling of new requests is
162+
* prevented and the given {@code callback} is invoked at the end of the attempt. The
163+
* attempt can be explicitly ended by invoking {@link #stop}.
164+
* <p>
165+
* Once shutdown has been initiated Netty will reject any new connections. Requests +
166+
* on existing idle connections will also be rejected.
167+
*/
160168
@Override
161169
public void shutDownGracefully(GracefulShutdownCallback callback) {
162170
if (this.gracefulShutdown == null) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -383,6 +383,14 @@ public Tomcat getTomcat() {
383383
return this.tomcat;
384384
}
385385

386+
/**
387+
* Initiates a graceful shutdown of the Tomcat web server. Handling of new requests is
388+
* prevented and the given {@code callback} is invoked at the end of the attempt. The
389+
* attempt can be explicitly ended by invoking {@link #stop}.
390+
* <p>
391+
* Once shutdown has been initiated Tomcat will reject any new connections. Requests
392+
* on existing idle connections will also be rejected.
393+
*/
386394
@Override
387395
public void shutDownGracefully(GracefulShutdownCallback callback) {
388396
if (this.gracefulShutdown == null) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java

+8
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,14 @@ public int getPort() {
289289
return ports.get(0).getNumber();
290290
}
291291

292+
/**
293+
* Initiates a graceful shutdown of the Undertow web server. Handling of new requests
294+
* is prevented and the given {@code callback} is invoked at the end of the attempt.
295+
* The attempt can be explicitly ended by invoking {@link #stop}.
296+
* <p>
297+
* Once shutdown has been initiated Undertow will return an {@code HTTP 503} response
298+
* for any new or existing connections.
299+
*/
292300
@Override
293301
public void shutDownGracefully(GracefulShutdownCallback callback) {
294302
if (this.gracefulShutdown == null) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@
3636
import io.undertow.servlet.api.DeploymentInfo;
3737
import io.undertow.servlet.api.ServletContainer;
3838
import jakarta.servlet.ServletRegistration.Dynamic;
39+
import org.apache.hc.client5.http.classic.HttpClient;
40+
import org.apache.hc.client5.http.impl.classic.HttpClients;
3941
import org.apache.hc.core5.http.HttpResponse;
4042
import org.apache.jasper.servlet.JspServlet;
4143
import org.awaitility.Awaitility;
@@ -211,6 +213,35 @@ void whenServerIsShuttingDownGracefullyThenRequestsAreRejectedWithServiceUnavail
211213
this.webServer.stop();
212214
}
213215

216+
@Test
217+
void whenServerIsShuttingDownARequestOnAnIdleConnectionAreRejectedWithServiceUnavailable() throws Exception {
218+
AbstractServletWebServerFactory factory = getFactory();
219+
factory.setShutdown(Shutdown.GRACEFUL);
220+
BlockingServlet blockingServlet = new BlockingServlet();
221+
this.webServer = factory.getWebServer((context) -> {
222+
Dynamic registration = context.addServlet("blockingServlet", blockingServlet);
223+
registration.addMapping("/blocking");
224+
registration.setAsyncSupported(true);
225+
});
226+
HttpClient httpClient = HttpClients.createMinimal();
227+
this.webServer.start();
228+
int port = this.webServer.getPort();
229+
Future<Object> keepAliveRequest = initiateGetRequest(httpClient, port, "/blocking");
230+
blockingServlet.awaitQueue();
231+
blockingServlet.admitOne();
232+
assertThat(keepAliveRequest.get()).isInstanceOf(HttpResponse.class);
233+
Future<Object> request = initiateGetRequest(port, "/blocking");
234+
blockingServlet.awaitQueue();
235+
this.webServer.shutDownGracefully((result) -> {
236+
});
237+
HttpResponse idleConnectionResponse = (HttpResponse) initiateGetRequest(httpClient, port, "/").get();
238+
assertThat(idleConnectionResponse.getCode()).isEqualTo(503);
239+
blockingServlet.admitOne();
240+
Object response = request.get();
241+
assertThat(response).isInstanceOf(HttpResponse.class);
242+
this.webServer.stop();
243+
}
244+
214245
private void testAccessLog(String prefix, String suffix, String expectedFile)
215246
throws IOException, URISyntaxException {
216247
UndertowServletWebServerFactory factory = getFactory();

0 commit comments

Comments
 (0)