Skip to content

Commit 9ba78db

Browse files
committed
Allow Undertow to stop when a request is being handled
Previously, unlike embedded Jetty, Netty, and Tomcat, Undertow would not stop when one of its worker threads was in use. This meant that a a long-running or stalled request could prevent the application from shutting down in response to SIGTERM or SIGINT, and SIGTERM would be required to get the process to exit. This commit updates the factories for the reactive and servlet Undertow web server factories to configure Undertow to use a 0ms shutdown timeout. This aligns it with the behaviour of Jetty, Netty, and Tomcat. Tests have been introduced to verify the behaviour across the reactive and servlet variants of all four supported embedded web servers. Fixes gh-21319
1 parent 43e7ccd commit 9ba78db

File tree

4 files changed

+72
-2
lines changed

4 files changed

+72
-2
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -117,6 +117,7 @@ private Undertow.Builder createBuilder(int port) {
117117
else {
118118
builder.addHttpListener(port, getListenAddress());
119119
}
120+
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
120121
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
121122
customizer.customize(builder);
122123
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -241,6 +241,7 @@ private Builder createBuilder(int port) {
241241
else {
242242
builder.addHttpListener(port, getListenAddress());
243243
}
244+
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
244245
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
245246
customizer.customize(builder);
246247
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
import java.security.KeyStore;
2424
import java.time.Duration;
2525
import java.util.Arrays;
26+
import java.util.concurrent.BrokenBarrierException;
2627
import java.util.concurrent.Callable;
28+
import java.util.concurrent.CountDownLatch;
29+
import java.util.concurrent.CyclicBarrier;
2730

2831
import javax.net.ssl.KeyManagerFactory;
2932
import javax.net.ssl.SSLException;
@@ -333,6 +336,31 @@ void whenSslIsEnabledAndNoKeyStoreIsConfiguredThenServerFailsToStart() {
333336
.hasMessageContaining("Could not load key store 'null'");
334337
}
335338

339+
@Test
340+
void whenARequestIsActiveThenStopWillComplete() throws InterruptedException, BrokenBarrierException {
341+
AbstractReactiveWebServerFactory factory = getFactory();
342+
CyclicBarrier barrier = new CyclicBarrier(2);
343+
CountDownLatch latch = new CountDownLatch(1);
344+
this.webServer = factory.getWebServer((request, response) -> {
345+
try {
346+
barrier.await();
347+
latch.await();
348+
}
349+
catch (InterruptedException ex) {
350+
Thread.currentThread().interrupt();
351+
}
352+
catch (BrokenBarrierException ex) {
353+
throw new IllegalStateException(ex);
354+
}
355+
return response.setComplete();
356+
});
357+
this.webServer.start();
358+
new Thread(() -> getWebClient().build().get().uri("/").exchange().block()).start();
359+
barrier.await();
360+
this.webServer.stop();
361+
latch.countDown();
362+
}
363+
336364
protected WebClient prepareCompressionTest() {
337365
Compression compression = new Compression();
338366
compression.setEnabled(true);

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@
4545
import java.util.HashMap;
4646
import java.util.Locale;
4747
import java.util.Map;
48+
import java.util.concurrent.BrokenBarrierException;
4849
import java.util.concurrent.Callable;
50+
import java.util.concurrent.CountDownLatch;
51+
import java.util.concurrent.CyclicBarrier;
4952
import java.util.concurrent.atomic.AtomicBoolean;
5053
import java.util.concurrent.atomic.AtomicReference;
5154
import java.util.zip.GZIPInputStream;
@@ -986,6 +989,43 @@ void exceptionThrownOnLoadFailureIsRethrown() {
986989
.satisfies(this::wrapsFailingServletException);
987990
}
988991

992+
@Test
993+
void whenARequestIsActiveThenStopWillComplete() throws InterruptedException, BrokenBarrierException {
994+
AbstractServletWebServerFactory factory = getFactory();
995+
CyclicBarrier barrier = new CyclicBarrier(2);
996+
CountDownLatch latch = new CountDownLatch(1);
997+
this.webServer = factory.getWebServer((context) -> context.addServlet("blocking", new HttpServlet() {
998+
999+
@Override
1000+
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
1001+
throws ServletException, IOException {
1002+
try {
1003+
barrier.await();
1004+
latch.await();
1005+
}
1006+
catch (InterruptedException ex) {
1007+
Thread.currentThread().interrupt();
1008+
}
1009+
catch (BrokenBarrierException ex) {
1010+
throw new ServletException(ex);
1011+
}
1012+
}
1013+
1014+
}).addMapping("/"));
1015+
this.webServer.start();
1016+
new Thread(() -> {
1017+
try {
1018+
getResponse(getLocalUrl("/"));
1019+
}
1020+
catch (Exception ex) {
1021+
// Continue
1022+
}
1023+
}).start();
1024+
barrier.await();
1025+
this.webServer.stop();
1026+
latch.countDown();
1027+
}
1028+
9891029
private void wrapsFailingServletException(WebServerException ex) {
9901030
Throwable cause = ex.getCause();
9911031
while (cause != null) {

0 commit comments

Comments
 (0)