001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.context.request.async; 018 019import java.util.PriorityQueue; 020import java.util.concurrent.Callable; 021import java.util.function.Consumer; 022import java.util.function.Supplier; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.web.context.request.NativeWebRequest; 030 031/** 032 * {@code DeferredResult} provides an alternative to using a {@link Callable} for 033 * asynchronous request processing. While a {@code Callable} is executed concurrently 034 * on behalf of the application, with a {@code DeferredResult} the application can 035 * produce the result from a thread of its choice. 036 * 037 * <p>Subclasses can extend this class to easily associate additional data or behavior 038 * with the {@link DeferredResult}. For example, one might want to associate the user 039 * used to create the {@link DeferredResult} by extending the class and adding an 040 * additional property for the user. In this way, the user could easily be accessed 041 * later without the need to use a data structure to do the mapping. 042 * 043 * <p>An example of associating additional behavior to this class might be realized 044 * by extending the class to implement an additional interface. For example, one 045 * might want to implement {@link Comparable} so that when the {@link DeferredResult} 046 * is added to a {@link PriorityQueue} it is handled in the correct order. 047 * 048 * @author Rossen Stoyanchev 049 * @author Juergen Hoeller 050 * @author Rob Winch 051 * @since 3.2 052 * @param <T> the result type 053 */ 054public class DeferredResult<T> { 055 056 private static final Object RESULT_NONE = new Object(); 057 058 private static final Log logger = LogFactory.getLog(DeferredResult.class); 059 060 061 @Nullable 062 private final Long timeoutValue; 063 064 private final Supplier<?> timeoutResult; 065 066 private Runnable timeoutCallback; 067 068 private Consumer<Throwable> errorCallback; 069 070 private Runnable completionCallback; 071 072 private DeferredResultHandler resultHandler; 073 074 private volatile Object result = RESULT_NONE; 075 076 private volatile boolean expired = false; 077 078 079 /** 080 * Create a DeferredResult. 081 */ 082 public DeferredResult() { 083 this(null, () -> RESULT_NONE); 084 } 085 086 /** 087 * Create a DeferredResult with a custom timeout value. 088 * <p>By default not set in which case the default configured in the MVC 089 * Java Config or the MVC namespace is used, or if that's not set, then the 090 * timeout depends on the default of the underlying server. 091 * @param timeoutValue timeout value in milliseconds 092 */ 093 public DeferredResult(Long timeoutValue) { 094 this(timeoutValue, () -> RESULT_NONE); 095 } 096 097 /** 098 * Create a DeferredResult with a timeout value and a default result to use 099 * in case of timeout. 100 * @param timeoutValue timeout value in milliseconds (ignored if {@code null}) 101 * @param timeoutResult the result to use 102 */ 103 public DeferredResult(@Nullable Long timeoutValue, Object timeoutResult) { 104 this.timeoutValue = timeoutValue; 105 this.timeoutResult = () -> timeoutResult; 106 } 107 108 /** 109 * Variant of {@link #DeferredResult(Long, Object)} that accepts a dynamic 110 * fallback value based on a {@link Supplier}. 111 * @param timeoutValue timeout value in milliseconds (ignored if {@code null}) 112 * @param timeoutResult the result supplier to use 113 * @since 5.1.1 114 */ 115 public DeferredResult(@Nullable Long timeoutValue, Supplier<?> timeoutResult) { 116 this.timeoutValue = timeoutValue; 117 this.timeoutResult = timeoutResult; 118 } 119 120 121 /** 122 * Return {@code true} if this DeferredResult is no longer usable either 123 * because it was previously set or because the underlying request expired. 124 * <p>The result may have been set with a call to {@link #setResult(Object)}, 125 * or {@link #setErrorResult(Object)}, or as a result of a timeout, if a 126 * timeout result was provided to the constructor. The request may also 127 * expire due to a timeout or network error. 128 */ 129 public final boolean isSetOrExpired() { 130 return (this.result != RESULT_NONE || this.expired); 131 } 132 133 /** 134 * Return {@code true} if the DeferredResult has been set. 135 * @since 4.0 136 */ 137 public boolean hasResult() { 138 return (this.result != RESULT_NONE); 139 } 140 141 /** 142 * Return the result, or {@code null} if the result wasn't set. Since the result 143 * can also be {@code null}, it is recommended to use {@link #hasResult()} first 144 * to check if there is a result prior to calling this method. 145 * @since 4.0 146 */ 147 @Nullable 148 public Object getResult() { 149 Object resultToCheck = this.result; 150 return (resultToCheck != RESULT_NONE ? resultToCheck : null); 151 } 152 153 /** 154 * Return the configured timeout value in milliseconds. 155 */ 156 @Nullable 157 final Long getTimeoutValue() { 158 return this.timeoutValue; 159 } 160 161 /** 162 * Register code to invoke when the async request times out. 163 * <p>This method is called from a container thread when an async request 164 * times out before the {@code DeferredResult} has been populated. 165 * It may invoke {@link DeferredResult#setResult setResult} or 166 * {@link DeferredResult#setErrorResult setErrorResult} to resume processing. 167 */ 168 public void onTimeout(Runnable callback) { 169 this.timeoutCallback = callback; 170 } 171 172 /** 173 * Register code to invoke when an error occurred during the async request. 174 * <p>This method is called from a container thread when an error occurs 175 * while processing an async request before the {@code DeferredResult} has 176 * been populated. It may invoke {@link DeferredResult#setResult setResult} 177 * or {@link DeferredResult#setErrorResult setErrorResult} to resume 178 * processing. 179 * @since 5.0 180 */ 181 public void onError(Consumer<Throwable> callback) { 182 this.errorCallback = callback; 183 } 184 185 /** 186 * Register code to invoke when the async request completes. 187 * <p>This method is called from a container thread when an async request 188 * completed for any reason including timeout and network error. This is useful 189 * for detecting that a {@code DeferredResult} instance is no longer usable. 190 */ 191 public void onCompletion(Runnable callback) { 192 this.completionCallback = callback; 193 } 194 195 /** 196 * Provide a handler to use to handle the result value. 197 * @param resultHandler the handler 198 * @see DeferredResultProcessingInterceptor 199 */ 200 public final void setResultHandler(DeferredResultHandler resultHandler) { 201 Assert.notNull(resultHandler, "DeferredResultHandler is required"); 202 // Immediate expiration check outside of the result lock 203 if (this.expired) { 204 return; 205 } 206 Object resultToHandle; 207 synchronized (this) { 208 // Got the lock in the meantime: double-check expiration status 209 if (this.expired) { 210 return; 211 } 212 resultToHandle = this.result; 213 if (resultToHandle == RESULT_NONE) { 214 // No result yet: store handler for processing once it comes in 215 this.resultHandler = resultHandler; 216 return; 217 } 218 } 219 // If we get here, we need to process an existing result object immediately. 220 // The decision is made within the result lock; just the handle call outside 221 // of it, avoiding any deadlock potential with Servlet container locks. 222 try { 223 resultHandler.handleResult(resultToHandle); 224 } 225 catch (Throwable ex) { 226 logger.debug("Failed to process async result", ex); 227 } 228 } 229 230 /** 231 * Set the value for the DeferredResult and handle it. 232 * @param result the value to set 233 * @return {@code true} if the result was set and passed on for handling; 234 * {@code false} if the result was already set or the async request expired 235 * @see #isSetOrExpired() 236 */ 237 public boolean setResult(T result) { 238 return setResultInternal(result); 239 } 240 241 private boolean setResultInternal(Object result) { 242 // Immediate expiration check outside of the result lock 243 if (isSetOrExpired()) { 244 return false; 245 } 246 DeferredResultHandler resultHandlerToUse; 247 synchronized (this) { 248 // Got the lock in the meantime: double-check expiration status 249 if (isSetOrExpired()) { 250 return false; 251 } 252 // At this point, we got a new result to process 253 this.result = result; 254 resultHandlerToUse = this.resultHandler; 255 if (resultHandlerToUse == null) { 256 // No result handler set yet -> let the setResultHandler implementation 257 // pick up the result object and invoke the result handler for it. 258 return true; 259 } 260 // Result handler available -> let's clear the stored reference since 261 // we don't need it anymore. 262 this.resultHandler = null; 263 } 264 // If we get here, we need to process an existing result object immediately. 265 // The decision is made within the result lock; just the handle call outside 266 // of it, avoiding any deadlock potential with Servlet container locks. 267 resultHandlerToUse.handleResult(result); 268 return true; 269 } 270 271 /** 272 * Set an error value for the {@link DeferredResult} and handle it. 273 * The value may be an {@link Exception} or {@link Throwable} in which case 274 * it will be processed as if a handler raised the exception. 275 * @param result the error result value 276 * @return {@code true} if the result was set to the error value and passed on 277 * for handling; {@code false} if the result was already set or the async 278 * request expired 279 * @see #isSetOrExpired() 280 */ 281 public boolean setErrorResult(Object result) { 282 return setResultInternal(result); 283 } 284 285 286 final DeferredResultProcessingInterceptor getInterceptor() { 287 return new DeferredResultProcessingInterceptor() { 288 @Override 289 public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) { 290 boolean continueProcessing = true; 291 try { 292 if (timeoutCallback != null) { 293 timeoutCallback.run(); 294 } 295 } 296 finally { 297 Object value = timeoutResult.get(); 298 if (value != RESULT_NONE) { 299 continueProcessing = false; 300 try { 301 setResultInternal(value); 302 } 303 catch (Throwable ex) { 304 logger.debug("Failed to handle timeout result", ex); 305 } 306 } 307 } 308 return continueProcessing; 309 } 310 @Override 311 public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> deferredResult, Throwable t) { 312 try { 313 if (errorCallback != null) { 314 errorCallback.accept(t); 315 } 316 } 317 finally { 318 try { 319 setResultInternal(t); 320 } 321 catch (Throwable ex) { 322 logger.debug("Failed to handle error result", ex); 323 } 324 } 325 return false; 326 } 327 @Override 328 public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) { 329 expired = true; 330 if (completionCallback != null) { 331 completionCallback.run(); 332 } 333 } 334 }; 335 } 336 337 338 /** 339 * Handles a DeferredResult value when set. 340 */ 341 @FunctionalInterface 342 public interface DeferredResultHandler { 343 344 void handleResult(Object result); 345 } 346 347}