From 9eefdb80415768dc9fe41f410e320f1d8b30c187 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 3 Jan 2025 16:04:51 +0000 Subject: [PATCH] Synchronize in WebAsyncManager onError/onTimeout On connection loss, in a race between application thread and onError callback trying to set the DeferredResult and dispatch, the onError callback must not exit until dispatch completes. Currently, it may do so because the DeferredResult has checks to bypasses locking or even trying to dispatch if result is already set. Closes gh-34192 --- .../web/context/request/async/WebAsyncManager.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java index 10e98d38d3..721c81bf64 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java @@ -425,6 +425,11 @@ public final class WebAsyncManager { } try { interceptorChain.triggerAfterTimeout(this.asyncWebRequest, deferredResult); + synchronized (WebAsyncManager.this) { + // If application thread set the DeferredResult first in a race, + // we must still not return until setConcurrentResultAndDispatch is done + return; + } } catch (Throwable ex) { setConcurrentResultAndDispatch(ex); @@ -437,6 +442,11 @@ public final class WebAsyncManager { } try { interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex); + synchronized (WebAsyncManager.this) { + // If application thread set the DeferredResult first in a race, + // we must still not return until setConcurrentResultAndDispatch is done + return; + } } catch (Throwable interceptorEx) { setConcurrentResultAndDispatch(interceptorEx);