Skip to content

Commit

Permalink
Allow Undertow to stop when a request is being handled
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wilkinsona committed May 6, 2020
1 parent 43e7ccd commit 9ba78db
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 2 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -117,6 +117,7 @@ private Undertow.Builder createBuilder(int port) {
else {
builder.addHttpListener(port, getListenAddress());
}
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -241,6 +241,7 @@ private Builder createBuilder(int port) {
else {
builder.addHttpListener(port, getListenAddress());
}
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
Expand Down
Expand Up @@ -23,7 +23,10 @@
import java.security.KeyStore;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
Expand Down Expand Up @@ -333,6 +336,31 @@ void whenSslIsEnabledAndNoKeyStoreIsConfiguredThenServerFailsToStart() {
.hasMessageContaining("Could not load key store 'null'");
}

@Test
void whenARequestIsActiveThenStopWillComplete() throws InterruptedException, BrokenBarrierException {
AbstractReactiveWebServerFactory factory = getFactory();
CyclicBarrier barrier = new CyclicBarrier(2);
CountDownLatch latch = new CountDownLatch(1);
this.webServer = factory.getWebServer((request, response) -> {
try {
barrier.await();
latch.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (BrokenBarrierException ex) {
throw new IllegalStateException(ex);
}
return response.setComplete();
});
this.webServer.start();
new Thread(() -> getWebClient().build().get().uri("/").exchange().block()).start();
barrier.await();
this.webServer.stop();
latch.countDown();
}

protected WebClient prepareCompressionTest() {
Compression compression = new Compression();
compression.setEnabled(true);
Expand Down
Expand Up @@ -45,7 +45,10 @@
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
Expand Down Expand Up @@ -986,6 +989,43 @@ void exceptionThrownOnLoadFailureIsRethrown() {
.satisfies(this::wrapsFailingServletException);
}

@Test
void whenARequestIsActiveThenStopWillComplete() throws InterruptedException, BrokenBarrierException {
AbstractServletWebServerFactory factory = getFactory();
CyclicBarrier barrier = new CyclicBarrier(2);
CountDownLatch latch = new CountDownLatch(1);
this.webServer = factory.getWebServer((context) -> context.addServlet("blocking", new HttpServlet() {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
barrier.await();
latch.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (BrokenBarrierException ex) {
throw new ServletException(ex);
}
}

}).addMapping("/"));
this.webServer.start();
new Thread(() -> {
try {
getResponse(getLocalUrl("/"));
}
catch (Exception ex) {
// Continue
}
}).start();
barrier.await();
this.webServer.stop();
latch.countDown();
}

private void wrapsFailingServletException(WebServerException ex) {
Throwable cause = ex.getCause();
while (cause != null) {
Expand Down

0 comments on commit 9ba78db

Please sign in to comment.