Skip to content

Commit 2408981

Browse files
committed
Use SmartLifecycle for graceful web server shutdown
Closes gh-21325
1 parent c42571b commit 2408981

File tree

40 files changed

+792
-950
lines changed

40 files changed

+792
-950
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ public class ServerProperties {
100100
*/
101101
private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);
102102

103+
/**
104+
* Type of shutdown that the server will support.
105+
*/
106+
private Shutdown shutdown = Shutdown.IMMEDIATE;
107+
103108
@NestedConfigurationProperty
104109
private Ssl ssl;
105110

@@ -109,9 +114,6 @@ public class ServerProperties {
109114
@NestedConfigurationProperty
110115
private final Http2 http2 = new Http2();
111116

112-
@NestedConfigurationProperty
113-
private final Shutdown shutdown = new Shutdown();
114-
115117
private final Servlet servlet = new Servlet();
116118

117119
private final Tomcat tomcat = new Tomcat();
@@ -154,6 +156,14 @@ public void setMaxHttpHeaderSize(DataSize maxHttpHeaderSize) {
154156
this.maxHttpHeaderSize = maxHttpHeaderSize;
155157
}
156158

159+
public Shutdown getShutdown() {
160+
return this.shutdown;
161+
}
162+
163+
public void setShutdown(Shutdown shutdown) {
164+
this.shutdown = shutdown;
165+
}
166+
157167
public ErrorProperties getError() {
158168
return this.error;
159169
}
@@ -174,10 +184,6 @@ public Http2 getHttp2() {
174184
return this.http2;
175185
}
176186

177-
public Shutdown getShutdown() {
178-
return this.shutdown;
179-
}
180-
181187
public Servlet getServlet() {
182188
return this.servlet;
183189
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@
266266
"name": "server.ssl.trust-store-type",
267267
"description": "Type of the trust store."
268268
},
269+
{ "name": "server.shutdown",
270+
"defaultValue:": "immediate"
271+
},
269272
{
270273
"name": "server.tomcat.max-http-post-size",
271274
"type": "org.springframework.util.unit.DataSize",

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryCustomizerTests.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.autoconfigure.web.reactive;
1818

1919
import java.net.InetAddress;
20-
import java.time.Duration;
2120

2221
import org.junit.jupiter.api.BeforeEach;
2322
import org.junit.jupiter.api.Test;
@@ -76,13 +75,13 @@ void testCustomizeServerSsl() {
7675
}
7776

7877
@Test
79-
void whenGracePeriodPropertyIsSetThenGracePeriodIsCustomized() {
80-
this.properties.getShutdown().setGracePeriod(Duration.ofSeconds(30));
78+
void whenShutdownPropertyIsSetThenShutdownIsCustomized() {
79+
this.properties.setShutdown(Shutdown.GRACEFUL);
8180
ConfigurableReactiveWebServerFactory factory = mock(ConfigurableReactiveWebServerFactory.class);
8281
this.customizer.customize(factory);
8382
ArgumentCaptor<Shutdown> shutdownCaptor = ArgumentCaptor.forClass(Shutdown.class);
8483
verify(factory).setShutdown(shutdownCaptor.capture());
85-
assertThat(shutdownCaptor.getValue().getGracePeriod()).isEqualTo(Duration.ofSeconds(30));
84+
assertThat(shutdownCaptor.getValue()).isEqualTo(Shutdown.GRACEFUL);
8685
}
8786

8887
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.autoconfigure.web.servlet;
1818

1919
import java.io.File;
20-
import java.time.Duration;
2120
import java.util.HashMap;
2221
import java.util.Map;
2322

@@ -165,15 +164,15 @@ void sessionStoreDir() {
165164
}
166165

167166
@Test
168-
void whenGracePeriodPropertyIsSetThenGracePeriodIsCustomized() {
167+
void whenShutdownPropertyIsSetThenShutdownIsCustomized() {
169168
Map<String, String> map = new HashMap<>();
170-
map.put("server.shutdown.grace-period", "30s");
169+
map.put("server.shutdown", "graceful");
171170
bindProperties(map);
172171
ConfigurableServletWebServerFactory factory = mock(ConfigurableServletWebServerFactory.class);
173172
this.customizer.customize(factory);
174173
ArgumentCaptor<Shutdown> shutdownCaptor = ArgumentCaptor.forClass(Shutdown.class);
175174
verify(factory).setShutdown(shutdownCaptor.capture());
176-
assertThat(shutdownCaptor.getValue().getGracePeriod()).isEqualTo(Duration.ofSeconds(30));
175+
assertThat(shutdownCaptor.getValue()).isEqualTo(Shutdown.GRACEFUL);
177176
}
178177

179178
private void bindProperties(Map<String, String> map) {

spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3145,21 +3145,19 @@ You can learn more about the resource configuration on the client side in the <<
31453145
[[boot-features-graceful-shutdown]]
31463146
== Graceful shutdown
31473147
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.
3148-
When enabled, shutdown of the application will include a grace period of configurable duration.
3149-
During this grace period, existing requests will be allowed to complete but no new requests will be permitted.
3148+
It occurs as part of closing the application context and is performed in the earliest phase of stopping `SmartLifecycle` beans.
3149+
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.
31503150
The exact way in which new requests are not permitted varies depending on the web server that is being used.
31513151
Jetty, Reactor Netty, and Tomcat will stop accepting requests at the network layer.
31523152
Undertow will accept requests but respond immediately with a service unavailable (503) response.
31533153

31543154
NOTE: Graceful shutdown with Tomcat requires Tomcat 9.0.33 or later.
31553155

3156-
Graceful shutdown occurs as one of the first steps during application close processing and before any beans have been destroyed.
3157-
This ensures that the beans are available for use by any processing that occurs while in-flight requests are being allowed to complete.
3158-
To enable graceful shutdown, configure the configprop:server.shutdown.grace-period[] property, as shown in the following example:
3156+
To enable graceful shutdown, configure the configprop:server.shutdown[] property, as shown in the following example:
31593157

31603158
[source,properties,indent=0,configprops]
31613159
----
3162-
server.shutdown.grace-period=30s
3160+
server.shutdown=graceful
31633161
----
31643162

31653163

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

Lines changed: 0 additions & 99 deletions
This file was deleted.

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@
3232
import org.eclipse.jetty.server.Server;
3333
import org.eclipse.jetty.server.ServerConnector;
3434
import org.eclipse.jetty.server.handler.HandlerWrapper;
35+
import org.eclipse.jetty.server.handler.StatisticsHandler;
3536
import org.eclipse.jetty.servlet.ServletContextHandler;
3637
import org.eclipse.jetty.servlet.ServletHolder;
3738
import org.eclipse.jetty.util.thread.ThreadPool;
3839

3940
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
4041
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
42+
import org.springframework.boot.web.server.Shutdown;
4143
import org.springframework.boot.web.server.WebServer;
4244
import org.springframework.http.client.reactive.JettyResourceFactory;
4345
import org.springframework.http.server.reactive.HttpHandler;
@@ -103,7 +105,7 @@ public void setAcceptors(int acceptors) {
103105
public WebServer getWebServer(HttpHandler httpHandler) {
104106
JettyHttpHandlerAdapter servlet = new JettyHttpHandlerAdapter(httpHandler);
105107
Server server = createJettyServer(servlet);
106-
return new JettyWebServer(server, getPort() >= 0, getShutdown().getGracePeriod());
108+
return new JettyWebServer(server, getPort() >= 0);
107109
}
108110

109111
@Override
@@ -182,6 +184,11 @@ protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
182184
if (this.useForwardHeaders) {
183185
new ForwardHeadersCustomizer().customize(server);
184186
}
187+
if (getShutdown() == Shutdown.GRACEFUL) {
188+
StatisticsHandler statisticsHandler = new StatisticsHandler();
189+
statisticsHandler.setHandler(server.getHandler());
190+
server.setHandler(statisticsHandler);
191+
}
185192
return server;
186193
}
187194

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.eclipse.jetty.server.ServerConnector;
4242
import org.eclipse.jetty.server.handler.ErrorHandler;
4343
import org.eclipse.jetty.server.handler.HandlerWrapper;
44+
import org.eclipse.jetty.server.handler.StatisticsHandler;
4445
import org.eclipse.jetty.server.session.DefaultSessionCache;
4546
import org.eclipse.jetty.server.session.FileSessionDataStore;
4647
import org.eclipse.jetty.server.session.SessionHandler;
@@ -57,6 +58,7 @@
5758

5859
import org.springframework.boot.web.server.ErrorPage;
5960
import org.springframework.boot.web.server.MimeMappings;
61+
import org.springframework.boot.web.server.Shutdown;
6062
import org.springframework.boot.web.server.WebServer;
6163
import org.springframework.boot.web.servlet.ServletContextInitializer;
6264
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
@@ -152,6 +154,11 @@ public WebServer getWebServer(ServletContextInitializer... initializers) {
152154
if (this.useForwardHeaders) {
153155
new ForwardHeadersCustomizer().customize(server);
154156
}
157+
if (getShutdown() == Shutdown.GRACEFUL) {
158+
StatisticsHandler statisticsHandler = new StatisticsHandler();
159+
statisticsHandler.setHandler(server.getHandler());
160+
server.setHandler(statisticsHandler);
161+
}
155162
return getJettyWebServer(server);
156163
}
157164

@@ -398,7 +405,7 @@ protected void postProcessWebAppContext(WebAppContext webAppContext) {
398405
* @return a new {@link JettyWebServer} instance
399406
*/
400407
protected JettyWebServer getJettyWebServer(Server server) {
401-
return new JettyWebServer(server, getPort() >= 0, getShutdown().getGracePeriod());
408+
return new JettyWebServer(server, getPort() >= 0);
402409
}
403410

404411
@Override

0 commit comments

Comments
 (0)