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
This commit is contained in:
Andy Wilkinson
2025-12-08 11:49:53 +00:00
parent a6986a4b57
commit 0cb86e1627
2 changed files with 21 additions and 6 deletions
@@ -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);
}
}
}
@@ -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