From 3ed981ebfdf2c83160c57286d3e8fac5363557d9 Mon Sep 17 00:00:00 2001 From: baranowb Date: Mon, 30 Jun 2025 11:14:52 +0200 Subject: [PATCH] [UNDERTOW-1870] Backport of RFE with property instead of API change --- .../servlet/spec/AsyncContextImpl.java | 3 +- .../onTimeout/property/AsyncServlet.java | 57 +++++++++++ .../property/DefaultAsyncTimeoutTestCase.java | 94 +++++++++++++++++++ .../property/SimpleAsyncListener.java | 58 ++++++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/AsyncServlet.java create mode 100644 servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/DefaultAsyncTimeoutTestCase.java create mode 100644 servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/SimpleAsyncListener.java diff --git a/servlet/src/main/java/io/undertow/servlet/spec/AsyncContextImpl.java b/servlet/src/main/java/io/undertow/servlet/spec/AsyncContextImpl.java index f50390bb3a..92ab3b7a30 100644 --- a/servlet/src/main/java/io/undertow/servlet/spec/AsyncContextImpl.java +++ b/servlet/src/main/java/io/undertow/servlet/spec/AsyncContextImpl.java @@ -87,7 +87,7 @@ public class AsyncContextImpl implements AsyncContext { //todo: make default configurable private volatile long timeout = 30000; - + public static final String ASYNC_CONTEXT_TIMEOUT = "io.undertow.servlet.ASYNC_CONTEXT_TIMEOUT"; private volatile XnioExecutor.Key timeoutKey; private boolean dispatched; @@ -106,6 +106,7 @@ public AsyncContextImpl(final HttpServerExchange exchange, final ServletRequest this.servletRequestContext = servletRequestContext; this.requestSupplied = requestSupplied; this.previousAsyncContext = previousAsyncContext; + this.timeout = Integer.parseInt(System.getProperty(ASYNC_CONTEXT_TIMEOUT, timeout+"")); initiatingThread = Thread.currentThread(); exchange.dispatch(SameThreadExecutor.INSTANCE, new Runnable() { @Override diff --git a/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/AsyncServlet.java b/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/AsyncServlet.java new file mode 100644 index 0000000000..b230e42883 --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/AsyncServlet.java @@ -0,0 +1,57 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2025 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.undertow.servlet.test.listener.request.async.onTimeout.property; + +import java.io.IOException; + +import io.undertow.servlet.spec.AsyncContextImpl; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * @author baranowb + */ +public class AsyncServlet extends HttpServlet { + + public static final String TEST_TIMEOUT="timeout"; + public static final String TIMEOUT_START_TSTAMP = "tstamp"; + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + long timeout = Long.parseLong((String)req.getParameter(TEST_TIMEOUT)); + System.setProperty(AsyncContextImpl.ASYNC_CONTEXT_TIMEOUT, timeout+""); + req.setAttribute(TIMEOUT_START_TSTAMP, System.currentTimeMillis()); + AsyncContext ctx = req.startAsync(); + ctx.addListener(new SimpleAsyncListener()); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(timeout+3000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.setProperty(AsyncContextImpl.ASYNC_CONTEXT_TIMEOUT, "30000"); + } + }); + t.start(); + } +} diff --git a/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/DefaultAsyncTimeoutTestCase.java b/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/DefaultAsyncTimeoutTestCase.java new file mode 100644 index 0000000000..81f776ec5d --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/DefaultAsyncTimeoutTestCase.java @@ -0,0 +1,94 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2025 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.undertow.servlet.test.listener.request.async.onTimeout.property; + +import java.io.IOException; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ListenerInfo; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.test.listener.request.async.onTimeout.SimpleRequestListener; +import io.undertow.servlet.test.util.TestClassIntrospector; +import io.undertow.testutils.DefaultServer; +import io.undertow.testutils.TestHttpClient; +import io.undertow.util.StatusCodes; +import jakarta.servlet.ServletException; + +@RunWith(DefaultServer.class) +public class DefaultAsyncTimeoutTestCase { + @BeforeClass + public static void setup() throws ServletException { + + final PathHandler root = new PathHandler(); + final ServletContainer container = ServletContainer.Factory.newInstance(); + + ServletInfo a = new ServletInfo("asyncServlet", AsyncServlet.class) + .setAsyncSupported(true) + .addMapping("/async"); + + DeploymentInfo builder = new DeploymentInfo() + .setClassLoader(DefaultAsyncTimeoutTestCase.class.getClassLoader()) + .setContextPath("/servletContext") + .setClassIntrospecter(TestClassIntrospector.INSTANCE) + .setDeploymentName("servletContext.war") + .addServlets(a) + .addListener(new ListenerInfo(SimpleRequestListener.class)); + + DeploymentManager manager = container.addDeployment(builder); + manager.deploy(); + root.addPrefixPath(builder.getContextPath(), manager.start()); + + DefaultServer.setRootHandler(root); + } + + @Test + public void testDefaultTimeout() throws IOException { + TestHttpClient client = new TestHttpClient(); + try { + HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async?"+AsyncServlet.TEST_TIMEOUT+"=30000"); + HttpResponse response = client.execute(get); + Assert.assertEquals(StatusCodes.OK, response.getStatusLine().getStatusCode()); + Assert.assertFalse(SimpleRequestListener.hasNestedInvocationOccured()); + } finally { + client.getConnectionManager().shutdown(); + } + } + + @Test + public void testNewDefaultTimeout() throws IOException { + TestHttpClient client = new TestHttpClient(); + try { + HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async?"+AsyncServlet.TEST_TIMEOUT+"=20000"); + HttpResponse response = client.execute(get); + Assert.assertEquals(StatusCodes.OK, response.getStatusLine().getStatusCode()); + Assert.assertFalse(SimpleRequestListener.hasNestedInvocationOccured()); + } finally { + client.getConnectionManager().shutdown(); + } + } +} diff --git a/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/SimpleAsyncListener.java b/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/SimpleAsyncListener.java new file mode 100644 index 0000000000..e51b496694 --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/property/SimpleAsyncListener.java @@ -0,0 +1,58 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2025 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.undertow.servlet.test.listener.request.async.onTimeout.property; + +import io.undertow.util.StatusCodes; + +import java.io.IOException; + +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.http.HttpServletResponse; + +public class SimpleAsyncListener implements AsyncListener { + + public static final String HEADER="TIMER_HEADER"; + @Override + public void onComplete(AsyncEvent event) throws IOException { + } + + @Override + public void onTimeout(AsyncEvent event) throws IOException { + HttpServletResponse response = (HttpServletResponse) event.getSuppliedResponse(); + final long startTstamp = (long) event.getSuppliedRequest().getAttribute(AsyncServlet.TIMEOUT_START_TSTAMP); + long timeout = Long.parseLong((String) event.getSuppliedRequest().getParameter(AsyncServlet.TEST_TIMEOUT)); + long elapsed = System.currentTimeMillis() - startTstamp; + response.setHeader(HEADER, (System.currentTimeMillis() - startTstamp) + ""); + if (Math.abs(timeout - elapsed) < 2000) { + response.setStatus(StatusCodes.OK); + } else { + response.setStatus(StatusCodes.CONFLICT); + } + event.getAsyncContext().complete(); + } + + @Override + public void onError(AsyncEvent event) throws IOException { + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException { + } + +}