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 /**
@@ -142,40 +143,72 @@ public void onCompletion(Runnable callback) {
142143 */
143144 public final void setResultHandler (DeferredResultHandler resultHandler ) {
144145 Assert .notNull (resultHandler , "DeferredResultHandler is required" );
146+ // Immediate expiration check outside of the result lock
147+ if (this .expired ) {
148+ return ;
149+ }
150+ Object resultToHandle ;
145151 synchronized (this ) {
146- this .resultHandler = resultHandler ;
147- if (this .result != RESULT_NONE && !this .expired ) {
148- try {
149- this .resultHandler .handleResult (this .result );
150- }
151- catch (Throwable ex ) {
152- logger .trace ("DeferredResult not handled" , ex );
153- }
152+ // Got the lock in the meantime: double-check expiration status
153+ if (this .expired ) {
154+ return ;
154155 }
156+ resultToHandle = this .result ;
157+ if (resultToHandle == RESULT_NONE ) {
158+ // No result yet: store handler for processing once it comes in
159+ this .resultHandler = resultHandler ;
160+ return ;
161+ }
162+ }
163+ // If we get here, we need to process an existing result object immediately.
164+ // The decision is made within the result lock; just the handle call outside
165+ // of it, avoiding any deadlock potential with Servlet container locks.
166+ try {
167+ resultHandler .handleResult (resultToHandle );
168+ }
169+ catch (Throwable ex ) {
170+ logger .debug ("Failed to handle existing result" , ex );
155171 }
156172 }
157173
158174 /**
159175 * Set the value for the DeferredResult and handle it.
160176 * @param result the value to set
161- * @return " true" if the result was set and passed on for handling;
162- * " false" if the result was already set or the async request expired
177+ * @return {@code true} if the result was set and passed on for handling;
178+ * {@code false} if the result was already set or the async request expired
163179 * @see #isSetOrExpired()
164180 */
165181 public boolean setResult (T result ) {
166182 return setResultInternal (result );
167183 }
168184
169185 private boolean setResultInternal (Object result ) {
186+ // Immediate expiration check outside of the result lock
187+ if (isSetOrExpired ()) {
188+ return false ;
189+ }
190+ DeferredResultHandler resultHandlerToUse ;
170191 synchronized (this ) {
192+ // Got the lock in the meantime: double-check expiration status
171193 if (isSetOrExpired ()) {
172194 return false ;
173195 }
196+ // At this point, we got a new result to process
174197 this .result = result ;
198+ resultHandlerToUse = this .resultHandler ;
199+ if (resultHandlerToUse == null ) {
200+ // No result handler set yet -> let the setResultHandler implementation
201+ // pick up the result object and invoke the result handler for it.
202+ return true ;
203+ }
204+ // Result handler available -> let's clear the stored reference since
205+ // we don't need it anymore.
206+ this .resultHandler = null ;
175207 }
176- if (this .resultHandler != null ) {
177- this .resultHandler .handleResult (this .result );
178- }
208+ // If we get here, we need to process an existing result object immediately.
209+ // The decision is made within the result lock; just the handle call outside
210+ // of it, avoiding any deadlock potential with Servlet container locks.
211+ resultHandlerToUse .handleResult (result );
179212 return true ;
180213 }
181214
@@ -184,8 +217,9 @@ private boolean setResultInternal(Object result) {
184217 * The value may be an {@link Exception} or {@link Throwable} in which case
185218 * it will be processed as if a handler raised the exception.
186219 * @param result the error result value
187- * @return "true" if the result was set to the error value and passed on for
188- * handling; "false" if the result was already set or the async request expired
220+ * @return {@code true} if the result was set to the error value and passed on
221+ * for handling; {@code false} if the result was already set or the async
222+ * request expired
189223 * @see #isSetOrExpired()
190224 */
191225 public boolean setErrorResult (Object result ) {
@@ -197,19 +231,28 @@ final DeferredResultProcessingInterceptor getInterceptor() {
197231 return new DeferredResultProcessingInterceptorAdapter () {
198232 @ Override
199233 public <S > boolean handleTimeout (NativeWebRequest request , DeferredResult <S > deferredResult ) {
200- if (timeoutCallback != null ) {
201- timeoutCallback .run ();
234+ boolean continueProcessing = true ;
235+ try {
236+ if (timeoutCallback != null ) {
237+ timeoutCallback .run ();
238+ }
202239 }
203- if (DeferredResult .this .timeoutResult != RESULT_NONE ) {
204- setResultInternal (timeoutResult );
240+ finally {
241+ if (timeoutResult != RESULT_NONE ) {
242+ continueProcessing = false ;
243+ try {
244+ setResultInternal (timeoutResult );
245+ }
246+ catch (Throwable ex ) {
247+ logger .debug ("Failed to handle timeout result" , ex );
248+ }
249+ }
205250 }
206- return true ;
251+ return continueProcessing ;
207252 }
208253 @ Override
209254 public <S > void afterCompletion (NativeWebRequest request , DeferredResult <S > deferredResult ) {
210- synchronized (DeferredResult .this ) {
211- expired = true ;
212- }
255+ expired = true ;
213256 if (completionCallback != null ) {
214257 completionCallback .run ();
215258 }
0 commit comments