22
22
import java .util .Map ;
23
23
import java .util .concurrent .Callable ;
24
24
import java .util .concurrent .Future ;
25
+ import java .util .concurrent .atomic .AtomicReference ;
25
26
26
27
import javax .servlet .http .HttpServletRequest ;
27
28
@@ -99,12 +100,7 @@ public final class WebAsyncManager {
99
100
@ Nullable
100
101
private volatile Object [] concurrentResultContext ;
101
102
102
- /*
103
- * Whether the concurrentResult is an error. If such errors remain unhandled, some
104
- * Servlet containers will call AsyncListener#onError at the end, after the ASYNC
105
- * and/or the ERROR dispatch (Boot's case), and we need to ignore those.
106
- */
107
- private volatile boolean errorHandlingInProgress ;
103
+ private final AtomicReference <State > state = new AtomicReference <>(State .NOT_STARTED );
108
104
109
105
private final Map <Object , CallableProcessingInterceptor > callableInterceptors = new LinkedHashMap <>();
110
106
@@ -257,6 +253,12 @@ public void registerDeferredResultInterceptors(DeferredResultProcessingIntercept
257
253
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
258
254
*/
259
255
public void clearConcurrentResult () {
256
+ if (!this .state .compareAndSet (State .RESULT_SET , State .NOT_STARTED )) {
257
+ if (logger .isDebugEnabled ()) {
258
+ logger .debug ("Unexpected call to clear: [" + this .state .get () + "]" );
259
+ }
260
+ return ;
261
+ }
260
262
synchronized (WebAsyncManager .this ) {
261
263
this .concurrentResult = RESULT_NONE ;
262
264
this .concurrentResultContext = null ;
@@ -297,6 +299,11 @@ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object..
297
299
Assert .notNull (webAsyncTask , "WebAsyncTask must not be null" );
298
300
Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
299
301
302
+ if (!this .state .compareAndSet (State .NOT_STARTED , State .ASYNC_PROCESSING )) {
303
+ throw new IllegalStateException (
304
+ "Unexpected call to startCallableProcessing: [" + this .state .get () + "]" );
305
+ }
306
+
300
307
Long timeout = webAsyncTask .getTimeout ();
301
308
if (timeout != null ) {
302
309
this .asyncWebRequest .setTimeout (timeout );
@@ -320,7 +327,7 @@ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object..
320
327
321
328
this .asyncWebRequest .addTimeoutHandler (() -> {
322
329
if (logger .isDebugEnabled ()) {
323
- logger .debug ("Async request timeout for " + formatUri (this .asyncWebRequest ));
330
+ logger .debug ("Servlet container timeout notification for " + formatUri (this .asyncWebRequest ));
324
331
}
325
332
Object result = interceptorChain .triggerAfterTimeout (this .asyncWebRequest , callable );
326
333
if (result != CallableProcessingInterceptor .RESULT_NONE ) {
@@ -329,14 +336,12 @@ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object..
329
336
});
330
337
331
338
this .asyncWebRequest .addErrorHandler (ex -> {
332
- if (!this .errorHandlingInProgress ) {
333
- if (logger .isDebugEnabled ()) {
334
- logger .debug ("Async request error for " + formatUri (this .asyncWebRequest ) + ": " + ex );
335
- }
336
- Object result = interceptorChain .triggerAfterError (this .asyncWebRequest , callable , ex );
337
- result = (result != CallableProcessingInterceptor .RESULT_NONE ? result : ex );
338
- setConcurrentResultAndDispatch (result );
339
+ if (logger .isDebugEnabled ()) {
340
+ logger .debug ("Servlet container error notification for " + formatUri (this .asyncWebRequest ) + ": " + ex );
339
341
}
342
+ Object result = interceptorChain .triggerAfterError (this .asyncWebRequest , callable , ex );
343
+ result = (result != CallableProcessingInterceptor .RESULT_NONE ? result : ex );
344
+ setConcurrentResultAndDispatch (result );
340
345
});
341
346
342
347
this .asyncWebRequest .addCompletionHandler (() ->
@@ -388,33 +393,40 @@ private void logExecutorWarning(AsyncWebRequest asyncWebRequest) {
388
393
}
389
394
390
395
private void setConcurrentResultAndDispatch (@ Nullable Object result ) {
396
+ Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
391
397
synchronized (WebAsyncManager .this ) {
392
- if (this .concurrentResult != RESULT_NONE ) {
398
+ if (!this .state .compareAndSet (State .ASYNC_PROCESSING , State .RESULT_SET )) {
399
+ if (logger .isDebugEnabled ()) {
400
+ logger .debug ("Async result already set: " +
401
+ "[" + this .state .get () + "], ignored result: " + result +
402
+ " for " + formatUri (this .asyncWebRequest ));
403
+ }
393
404
return ;
394
405
}
395
- this .concurrentResult = result ;
396
- this .errorHandlingInProgress = (result instanceof Throwable );
397
- }
398
406
399
- Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
400
- if (this .asyncWebRequest .isAsyncComplete ()) {
407
+ this .concurrentResult = result ;
401
408
if (logger .isDebugEnabled ()) {
402
- logger .debug ("Async result set but request already complete: " + formatUri (this .asyncWebRequest ));
409
+ logger .debug ("Async result set to: " + result + " for " + formatUri (this .asyncWebRequest ));
410
+ }
411
+
412
+ if (result instanceof Exception ) {
413
+ if (disconnectedClientHelper .checkAndLogClientDisconnectedException ((Exception ) result )) {
414
+ return ;
415
+ }
403
416
}
404
- return ;
405
- }
406
417
407
- if (result instanceof Exception ) {
408
- if (disconnectedClientHelper .checkAndLogClientDisconnectedException ((Exception ) result )) {
418
+ if (this .asyncWebRequest .isAsyncComplete ()) {
419
+ if (logger .isDebugEnabled ()) {
420
+ logger .debug ("Async request already completed for " + formatUri (this .asyncWebRequest ));
421
+ }
409
422
return ;
410
423
}
411
- }
412
424
413
- if (logger .isDebugEnabled ()) {
414
- logger .debug ("Async " + (this .errorHandlingInProgress ? "error" : "result set" ) +
415
- ", dispatch to " + formatUri (this .asyncWebRequest ));
425
+ if (logger .isDebugEnabled ()) {
426
+ logger .debug ("Performing async dispatch for " + formatUri (this .asyncWebRequest ));
427
+ }
428
+ this .asyncWebRequest .dispatch ();
416
429
}
417
- this .asyncWebRequest .dispatch ();
418
430
}
419
431
420
432
/**
@@ -437,6 +449,11 @@ public void startDeferredResultProcessing(
437
449
Assert .notNull (deferredResult , "DeferredResult must not be null" );
438
450
Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
439
451
452
+ if (!this .state .compareAndSet (State .NOT_STARTED , State .ASYNC_PROCESSING )) {
453
+ throw new IllegalStateException (
454
+ "Unexpected call to startDeferredResultProcessing: [" + this .state .get () + "]" );
455
+ }
456
+
440
457
Long timeout = deferredResult .getTimeoutValue ();
441
458
if (timeout != null ) {
442
459
this .asyncWebRequest .setTimeout (timeout );
@@ -450,6 +467,9 @@ public void startDeferredResultProcessing(
450
467
final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain (interceptors );
451
468
452
469
this .asyncWebRequest .addTimeoutHandler (() -> {
470
+ if (logger .isDebugEnabled ()) {
471
+ logger .debug ("Servlet container timeout notification for " + formatUri (this .asyncWebRequest ));
472
+ }
453
473
try {
454
474
interceptorChain .triggerAfterTimeout (this .asyncWebRequest , deferredResult );
455
475
}
@@ -459,16 +479,17 @@ public void startDeferredResultProcessing(
459
479
});
460
480
461
481
this .asyncWebRequest .addErrorHandler (ex -> {
462
- if (!this .errorHandlingInProgress ) {
463
- try {
464
- if (!interceptorChain .triggerAfterError (this .asyncWebRequest , deferredResult , ex )) {
465
- return ;
466
- }
467
- deferredResult .setErrorResult (ex );
468
- }
469
- catch (Throwable interceptorEx ) {
470
- setConcurrentResultAndDispatch (interceptorEx );
482
+ if (logger .isDebugEnabled ()) {
483
+ logger .debug ("Servlet container error notification for " + formatUri (this .asyncWebRequest ));
484
+ }
485
+ try {
486
+ if (!interceptorChain .triggerAfterError (this .asyncWebRequest , deferredResult , ex )) {
487
+ return ;
471
488
}
489
+ deferredResult .setErrorResult (ex );
490
+ }
491
+ catch (Throwable interceptorEx ) {
492
+ setConcurrentResultAndDispatch (interceptorEx );
472
493
}
473
494
});
474
495
@@ -494,10 +515,13 @@ private void startAsyncProcessing(Object[] processingContext) {
494
515
synchronized (WebAsyncManager .this ) {
495
516
this .concurrentResult = RESULT_NONE ;
496
517
this .concurrentResultContext = processingContext ;
497
- this .errorHandlingInProgress = false ;
498
518
}
499
519
500
520
Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
521
+ if (logger .isDebugEnabled ()) {
522
+ logger .debug ("Started async request for " + formatUri (this .asyncWebRequest ));
523
+ }
524
+
501
525
this .asyncWebRequest .startAsync ();
502
526
if (logger .isDebugEnabled ()) {
503
527
logger .debug ("Started async request" );
@@ -509,4 +533,31 @@ private static String formatUri(AsyncWebRequest asyncWebRequest) {
509
533
return (request != null ? request .getRequestURI () : "servlet container" );
510
534
}
511
535
536
+
537
+ /**
538
+ * Represents a state for {@link WebAsyncManager} to be in.
539
+ * <p><pre>
540
+ * NOT_STARTED <------+
541
+ * | |
542
+ * v |
543
+ * ASYNC_PROCESSING |
544
+ * | |
545
+ * v |
546
+ * RESULT_SET -------+
547
+ * </pre>
548
+ * @since 5.3.33
549
+ */
550
+ private enum State {
551
+
552
+ /** No async processing in progress. */
553
+ NOT_STARTED ,
554
+
555
+ /** Async handling has started, but the result hasn't been set yet. */
556
+ ASYNC_PROCESSING ,
557
+
558
+ /** The result is set, and an async dispatch was performed, unless there is a network error. */
559
+ RESULT_SET
560
+
561
+ }
562
+
512
563
}
0 commit comments