11/*
2- * Copyright 2002-2015 the original author or authors.
2+ * Copyright 2002-2016 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
4343 * is added to a {@link PriorityQueue} it is handled in the correct order.
4444 *
4545 * @author Rossen Stoyanchev
46+ * @author Juergen Hoeller
4647 * @author Rob Winch
4748 * @since 3.2
4849 */
@@ -65,7 +66,7 @@ public class DeferredResult<T> {
6566
6667 private volatile Object result = RESULT_NONE ;
6768
68- private volatile boolean expired ;
69+ private volatile boolean expired = false ;
6970
7071
7172 /**
@@ -164,40 +165,72 @@ public void onCompletion(Runnable callback) {
164165 */
165166 public final void setResultHandler (DeferredResultHandler resultHandler ) {
166167 Assert .notNull (resultHandler , "DeferredResultHandler is required" );
168+ // Immediate expiration check outside of the result lock
169+ if (this .expired ) {
170+ return ;
171+ }
172+ Object resultToHandle ;
167173 synchronized (this ) {
168- this .resultHandler = resultHandler ;
169- if (this .result != RESULT_NONE && !this .expired ) {
170- try {
171- this .resultHandler .handleResult (this .result );
172- }
173- catch (Throwable ex ) {
174- logger .trace ("DeferredResult not handled" , ex );
175- }
174+ // Got the lock in the meantime: double-check expiration status
175+ if (this .expired ) {
176+ return ;
176177 }
178+ resultToHandle = this .result ;
179+ if (resultToHandle == RESULT_NONE ) {
180+ // No result yet: store handler for processing once it comes in
181+ this .resultHandler = resultHandler ;
182+ return ;
183+ }
184+ }
185+ // If we get here, we need to process an existing result object immediately.
186+ // The decision is made within the result lock; just the handle call outside
187+ // of it, avoiding any deadlock potential with Servlet container locks.
188+ try {
189+ resultHandler .handleResult (resultToHandle );
190+ }
191+ catch (Throwable ex ) {
192+ logger .debug ("Failed to handle existing result" , ex );
177193 }
178194 }
179195
180196 /**
181197 * Set the value for the DeferredResult and handle it.
182198 * @param result the value to set
183- * @return " true" if the result was set and passed on for handling;
184- * " false" if the result was already set or the async request expired
199+ * @return {@code true} if the result was set and passed on for handling;
200+ * {@code false} if the result was already set or the async request expired
185201 * @see #isSetOrExpired()
186202 */
187203 public boolean setResult (T result ) {
188204 return setResultInternal (result );
189205 }
190206
191207 private boolean setResultInternal (Object result ) {
208+ // Immediate expiration check outside of the result lock
209+ if (isSetOrExpired ()) {
210+ return false ;
211+ }
212+ DeferredResultHandler resultHandlerToUse ;
192213 synchronized (this ) {
214+ // Got the lock in the meantime: double-check expiration status
193215 if (isSetOrExpired ()) {
194216 return false ;
195217 }
218+ // At this point, we got a new result to process
196219 this .result = result ;
220+ resultHandlerToUse = this .resultHandler ;
221+ if (resultHandlerToUse == null ) {
222+ // No result handler set yet -> let the setResultHandler implementation
223+ // pick up the result object and invoke the result handler for it.
224+ return true ;
225+ }
226+ // Result handler available -> let's clear the stored reference since
227+ // we don't need it anymore.
228+ this .resultHandler = null ;
197229 }
198- if (this .resultHandler != null ) {
199- this .resultHandler .handleResult (this .result );
200- }
230+ // If we get here, we need to process an existing result object immediately.
231+ // The decision is made within the result lock; just the handle call outside
232+ // of it, avoiding any deadlock potential with Servlet container locks.
233+ resultHandlerToUse .handleResult (result );
201234 return true ;
202235 }
203236
@@ -206,8 +239,9 @@ private boolean setResultInternal(Object result) {
206239 * The value may be an {@link Exception} or {@link Throwable} in which case
207240 * it will be processed as if a handler raised the exception.
208241 * @param result the error result value
209- * @return "true" if the result was set to the error value and passed on for
210- * handling; "false" if the result was already set or the async request expired
242+ * @return {@code true} if the result was set to the error value and passed on
243+ * for handling; {@code false} if the result was already set or the async
244+ * request expired
211245 * @see #isSetOrExpired()
212246 */
213247 public boolean setErrorResult (Object result ) {
@@ -219,19 +253,28 @@ final DeferredResultProcessingInterceptor getInterceptor() {
219253 return new DeferredResultProcessingInterceptorAdapter () {
220254 @ Override
221255 public <S > boolean handleTimeout (NativeWebRequest request , DeferredResult <S > deferredResult ) {
222- if (timeoutCallback != null ) {
223- timeoutCallback .run ();
256+ boolean continueProcessing = true ;
257+ try {
258+ if (timeoutCallback != null ) {
259+ timeoutCallback .run ();
260+ }
224261 }
225- if (DeferredResult .this .timeoutResult != RESULT_NONE ) {
226- setResultInternal (timeoutResult );
262+ finally {
263+ if (timeoutResult != RESULT_NONE ) {
264+ continueProcessing = false ;
265+ try {
266+ setResultInternal (timeoutResult );
267+ }
268+ catch (Throwable ex ) {
269+ logger .debug ("Failed to handle timeout result" , ex );
270+ }
271+ }
227272 }
228- return true ;
273+ return continueProcessing ;
229274 }
230275 @ Override
231276 public <S > void afterCompletion (NativeWebRequest request , DeferredResult <S > deferredResult ) {
232- synchronized (DeferredResult .this ) {
233- expired = true ;
234- }
277+ expired = true ;
235278 if (completionCallback != null ) {
236279 completionCallback .run ();
237280 }
0 commit comments