From 0cb86e162729328688efcea6bd62b4dcab491777 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 8 Dec 2025 11:36:12 +0000 Subject: [PATCH] Tolerate multiple calls to UndertowWebServer.destroy() Undertow's DeploymentManagerImpl is not idempotent. Calling undeploy() multiple times will result in an NPE. Similarly, calling start() or stop() after undeploy() will also result in an NPE. This commit protects against this by checking that the deployment manager's state is not UNDEPLOYED before calling stop() and undeploy(). Fixes gh-48446 --- .../DeploymentManagerHttpHandlerFactory.java | 15 +++++++++------ .../UndertowServletWebServerFactoryTests.java | 12 ++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/DeploymentManagerHttpHandlerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/DeploymentManagerHttpHandlerFactory.java index 58bd09fcd70..46b9c71a902 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/DeploymentManagerHttpHandlerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/DeploymentManagerHttpHandlerFactory.java @@ -22,6 +22,7 @@ import java.io.IOException; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.DeploymentManager.State; import jakarta.servlet.ServletException; import org.springframework.util.Assert; @@ -76,12 +77,14 @@ class DeploymentManagerHttpHandlerFactory implements HttpHandlerFactory { @Override public void close() throws IOException { - try { - this.deploymentManager.stop(); - this.deploymentManager.undeploy(); - } - catch (ServletException ex) { - throw new RuntimeException(ex); + if (this.deploymentManager.getState() != State.UNDEPLOYED) { + try { + this.deploymentManager.stop(); + this.deploymentManager.undeploy(); + } + catch (ServletException ex) { + throw new RuntimeException(ex); + } } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java index 8af9aec3931..c4280feee07 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java @@ -54,6 +54,7 @@ import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.GracefulShutdownResult; import org.springframework.boot.web.server.PortInUseException; import org.springframework.boot.web.server.Shutdown; +import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactoryTests; @@ -64,6 +65,7 @@ import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIOException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.inOrder; @@ -320,6 +322,16 @@ class UndertowServletWebServerFactoryTests extends AbstractServletWebServerFacto .isInstanceOfAny(SSLException.class, SocketException.class); } + @Test + void multipleCallsToDestroyAreTolerated() { + AbstractServletWebServerFactory factory = getFactory(); + factory.setPort(0); + WebServer webServer = factory.getWebServer(); + webServer.start(); + webServer.destroy(); + assertThatNoException().isThrownBy(webServer::destroy); + } + @Override protected JspServlet getJspServlet() { return null; // Undertow does not support JSPs