From 3c6b001349bfbaa7616b18cdd209aef16fa67a72 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 8 Sep 2026 11:48:40 +0200 Subject: [PATCH] Reuse existing async timeout in DefaultAsyncServerResponse Prior to this commit, calling `DefaultAsyncServerResponse.writeAsync()` would unconditionally create a new `AsyncWebRequest` and install it on the `WebAsyncManager`, even when one is already present for the current request. The functional web framework can do such a thing when returning a `ServerResponse.async(future)` from a `HandlerFunction`; the `HandlerFunctionAdapter` does install an async web request already. This means that the async timeout configured at the application level would be ignored and instead falling back to the Servlet container default. This commit makes the `DefaultAsyncServerResponse` skip async web request creation it there is an existing one. Fixes gh-37257 --- .../function/DefaultAsyncServerResponse.java | 7 +++-- .../DefaultAsyncServerResponseTests.java | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultAsyncServerResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultAsyncServerResponse.java index 065745bec96..1ba7faf03cc 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultAsyncServerResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultAsyncServerResponse.java @@ -115,8 +115,11 @@ final class DefaultAsyncServerResponse extends ErrorHandlingServerResponse imple throws ServletException, IOException { WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); - AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); - asyncManager.setAsyncWebRequest(asyncWebRequest); + AsyncWebRequest asyncWebRequest = asyncManager.getAsyncWebRequest(); + if (asyncWebRequest == null) { + asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); + asyncManager.setAsyncWebRequest(asyncWebRequest); + } try { asyncManager.startDeferredResultProcessing(deferredResult); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultAsyncServerResponseTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultAsyncServerResponseTests.java index 1a4d53b92f1..b0fead91860 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultAsyncServerResponseTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultAsyncServerResponseTests.java @@ -18,15 +18,43 @@ package org.springframework.web.servlet.function; import java.util.concurrent.CompletableFuture; +import jakarta.servlet.AsyncContext; import org.junit.jupiter.api.Test; +import org.springframework.web.context.request.async.AsyncWebRequest; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.WebAsyncManager; +import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.testfixture.servlet.MockHttpServletRequest; +import org.springframework.web.testfixture.servlet.MockHttpServletResponse; + import static org.assertj.core.api.Assertions.assertThat; /** + * Tests for {@link DefaultAsyncServerResponse}. * @author Arjen Poutsma */ class DefaultAsyncServerResponseTests { + @Test + void writeAsyncReusesExistingAsyncWebRequestTimeout() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); + request.setAsyncSupported(true); + MockHttpServletResponse response = new MockHttpServletResponse(); + + AsyncWebRequest existingAsyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); + existingAsyncWebRequest.setTimeout(1000L); + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + asyncManager.setAsyncWebRequest(existingAsyncWebRequest); + + DeferredResult deferredResult = new DeferredResult<>(); + DefaultAsyncServerResponse.writeAsync(request, response, deferredResult); + + assertThat(asyncManager.getAsyncWebRequest()).isSameAs(existingAsyncWebRequest); + AsyncContext asyncContext = request.getAsyncContext(); + assertThat(asyncContext.getTimeout()).isEqualTo(1000L); + } + @Test void blockCompleted() { ServerResponse wrappee = ServerResponse.ok().build();