From 6ec7dcf2c1ca98613999621f64f9be0960d19407 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 beed2f5976e..2cb1ae18ea0 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 @@ -427,6 +427,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); @@ -439,6 +444,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);