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 jakarta .servlet .http .HttpServletRequest ;
27
28
import org .apache .commons .logging .Log ;
34
35
import org .springframework .util .Assert ;
35
36
import org .springframework .web .context .request .RequestAttributes ;
36
37
import org .springframework .web .context .request .async .DeferredResult .DeferredResultHandler ;
37
- import org .springframework .web .util .DisconnectedClientHelper ;
38
38
39
39
/**
40
40
* The central class for managing asynchronous request processing, mainly intended
@@ -68,16 +68,6 @@ public final class WebAsyncManager {
68
68
69
69
private static final Log logger = LogFactory .getLog (WebAsyncManager .class );
70
70
71
- /**
72
- * Log category to use for network failure after a client has gone away.
73
- * @see DisconnectedClientHelper
74
- */
75
- private static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
76
- "org.springframework.web.server.DisconnectedClient" ;
77
-
78
- private static final DisconnectedClientHelper disconnectedClientHelper =
79
- new DisconnectedClientHelper (DISCONNECTED_CLIENT_LOG_CATEGORY );
80
-
81
71
private static final CallableProcessingInterceptor timeoutCallableInterceptor =
82
72
new TimeoutCallableProcessingInterceptor ();
83
73
@@ -98,12 +88,7 @@ public final class WebAsyncManager {
98
88
@ Nullable
99
89
private volatile Object [] concurrentResultContext ;
100
90
101
- /*
102
- * Whether the concurrentResult is an error. If such errors remain unhandled, some
103
- * Servlet containers will call AsyncListener#onError at the end, after the ASYNC
104
- * and/or the ERROR dispatch (Boot's case), and we need to ignore those.
105
- */
106
- private volatile boolean errorHandlingInProgress ;
91
+ private final AtomicReference <State > state = new AtomicReference <>(State .NOT_STARTED );
107
92
108
93
private final Map <Object , CallableProcessingInterceptor > callableInterceptors = new LinkedHashMap <>();
109
94
@@ -265,6 +250,12 @@ public void registerDeferredResultInterceptors(DeferredResultProcessingIntercept
265
250
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
266
251
*/
267
252
public void clearConcurrentResult () {
253
+ if (!this .state .compareAndSet (State .RESULT_SET , State .NOT_STARTED )) {
254
+ if (logger .isDebugEnabled ()) {
255
+ logger .debug ("Unexpected call to clear: [" + this .state .get () + "]" );
256
+ }
257
+ return ;
258
+ }
268
259
synchronized (WebAsyncManager .this ) {
269
260
this .concurrentResult = RESULT_NONE ;
270
261
this .concurrentResultContext = null ;
@@ -305,6 +296,11 @@ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object..
305
296
Assert .notNull (webAsyncTask , "WebAsyncTask must not be null" );
306
297
Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
307
298
299
+ if (!this .state .compareAndSet (State .NOT_STARTED , State .ASYNC_PROCESSING )) {
300
+ throw new IllegalStateException (
301
+ "Unexpected call to startCallableProcessing: [" + this .state .get () + "]" );
302
+ }
303
+
308
304
Long timeout = webAsyncTask .getTimeout ();
309
305
if (timeout != null ) {
310
306
this .asyncWebRequest .setTimeout (timeout );
@@ -328,7 +324,7 @@ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object..
328
324
329
325
this .asyncWebRequest .addTimeoutHandler (() -> {
330
326
if (logger .isDebugEnabled ()) {
331
- logger .debug ("Async request timeout for " + formatUri (this .asyncWebRequest ));
327
+ logger .debug ("Servlet container timeout notification for " + formatUri (this .asyncWebRequest ));
332
328
}
333
329
Object result = interceptorChain .triggerAfterTimeout (this .asyncWebRequest , callable );
334
330
if (result != CallableProcessingInterceptor .RESULT_NONE ) {
@@ -337,14 +333,12 @@ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object..
337
333
});
338
334
339
335
this .asyncWebRequest .addErrorHandler (ex -> {
340
- if (!this .errorHandlingInProgress ) {
341
- if (logger .isDebugEnabled ()) {
342
- logger .debug ("Async request error for " + formatUri (this .asyncWebRequest ) + ": " + ex );
343
- }
344
- Object result = interceptorChain .triggerAfterError (this .asyncWebRequest , callable , ex );
345
- result = (result != CallableProcessingInterceptor .RESULT_NONE ? result : ex );
346
- setConcurrentResultAndDispatch (result );
336
+ if (logger .isDebugEnabled ()) {
337
+ logger .debug ("Servlet container error notification for " + formatUri (this .asyncWebRequest ) + ": " + ex );
347
338
}
339
+ Object result = interceptorChain .triggerAfterError (this .asyncWebRequest , callable , ex );
340
+ result = (result != CallableProcessingInterceptor .RESULT_NONE ? result : ex );
341
+ setConcurrentResultAndDispatch (result );
348
342
});
349
343
350
344
this .asyncWebRequest .addCompletionHandler (() ->
@@ -396,31 +390,34 @@ private void logExecutorWarning(AsyncWebRequest asyncWebRequest) {
396
390
}
397
391
398
392
private void setConcurrentResultAndDispatch (@ Nullable Object result ) {
393
+ Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
399
394
synchronized (WebAsyncManager .this ) {
400
- if (this .concurrentResult != RESULT_NONE ) {
395
+ if (!this .state .compareAndSet (State .ASYNC_PROCESSING , State .RESULT_SET )) {
396
+ if (logger .isDebugEnabled ()) {
397
+ logger .debug ("Async result already set: " +
398
+ "[" + this .state .get () + "], ignored result: " + result +
399
+ " for " + formatUri (this .asyncWebRequest ));
400
+ }
401
401
return ;
402
402
}
403
- this .concurrentResult = result ;
404
- this .errorHandlingInProgress = (result instanceof Throwable );
405
- }
406
403
407
- Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
408
- if (this .asyncWebRequest .isAsyncComplete ()) {
404
+ this .concurrentResult = result ;
409
405
if (logger .isDebugEnabled ()) {
410
- logger .debug ("Async result set but request already complete: " + formatUri (this .asyncWebRequest ));
406
+ logger .debug ("Async result set to: " + result + " for " + formatUri (this .asyncWebRequest ));
411
407
}
412
- return ;
413
- }
414
408
415
- if (result instanceof Exception ex && disconnectedClientHelper .checkAndLogClientDisconnectedException (ex )) {
416
- return ;
417
- }
409
+ if (this .asyncWebRequest .isAsyncComplete ()) {
410
+ if (logger .isDebugEnabled ()) {
411
+ logger .debug ("Async request already completed for " + formatUri (this .asyncWebRequest ));
412
+ }
413
+ return ;
414
+ }
418
415
419
- if (logger .isDebugEnabled ()) {
420
- logger .debug ("Async " + (this .errorHandlingInProgress ? "error" : "result set" ) +
421
- ", dispatch to " + formatUri (this .asyncWebRequest ));
416
+ if (logger .isDebugEnabled ()) {
417
+ logger .debug ("Performing async dispatch for " + formatUri (this .asyncWebRequest ));
418
+ }
419
+ this .asyncWebRequest .dispatch ();
422
420
}
423
- this .asyncWebRequest .dispatch ();
424
421
}
425
422
426
423
/**
@@ -443,6 +440,11 @@ public void startDeferredResultProcessing(
443
440
Assert .notNull (deferredResult , "DeferredResult must not be null" );
444
441
Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
445
442
443
+ if (!this .state .compareAndSet (State .NOT_STARTED , State .ASYNC_PROCESSING )) {
444
+ throw new IllegalStateException (
445
+ "Unexpected call to startDeferredResultProcessing: [" + this .state .get () + "]" );
446
+ }
447
+
446
448
Long timeout = deferredResult .getTimeoutValue ();
447
449
if (timeout != null ) {
448
450
this .asyncWebRequest .setTimeout (timeout );
@@ -456,6 +458,9 @@ public void startDeferredResultProcessing(
456
458
final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain (interceptors );
457
459
458
460
this .asyncWebRequest .addTimeoutHandler (() -> {
461
+ if (logger .isDebugEnabled ()) {
462
+ logger .debug ("Servlet container timeout notification for " + formatUri (this .asyncWebRequest ));
463
+ }
459
464
try {
460
465
interceptorChain .triggerAfterTimeout (this .asyncWebRequest , deferredResult );
461
466
}
@@ -465,16 +470,17 @@ public void startDeferredResultProcessing(
465
470
});
466
471
467
472
this .asyncWebRequest .addErrorHandler (ex -> {
468
- if (!this .errorHandlingInProgress ) {
469
- try {
470
- if (!interceptorChain .triggerAfterError (this .asyncWebRequest , deferredResult , ex )) {
471
- return ;
472
- }
473
- deferredResult .setErrorResult (ex );
474
- }
475
- catch (Throwable interceptorEx ) {
476
- setConcurrentResultAndDispatch (interceptorEx );
473
+ if (logger .isDebugEnabled ()) {
474
+ logger .debug ("Servlet container error notification for " + formatUri (this .asyncWebRequest ));
475
+ }
476
+ try {
477
+ if (!interceptorChain .triggerAfterError (this .asyncWebRequest , deferredResult , ex )) {
478
+ return ;
477
479
}
480
+ deferredResult .setErrorResult (ex );
481
+ }
482
+ catch (Throwable interceptorEx ) {
483
+ setConcurrentResultAndDispatch (interceptorEx );
478
484
}
479
485
});
480
486
@@ -500,10 +506,13 @@ private void startAsyncProcessing(Object[] processingContext) {
500
506
synchronized (WebAsyncManager .this ) {
501
507
this .concurrentResult = RESULT_NONE ;
502
508
this .concurrentResultContext = processingContext ;
503
- this .errorHandlingInProgress = false ;
504
509
}
505
510
506
511
Assert .state (this .asyncWebRequest != null , "AsyncWebRequest must not be null" );
512
+ if (logger .isDebugEnabled ()) {
513
+ logger .debug ("Started async request for " + formatUri (this .asyncWebRequest ));
514
+ }
515
+
507
516
this .asyncWebRequest .startAsync ();
508
517
if (logger .isDebugEnabled ()) {
509
518
logger .debug ("Started async request" );
@@ -515,4 +524,31 @@ private static String formatUri(AsyncWebRequest asyncWebRequest) {
515
524
return (request != null ? request .getRequestURI () : "servlet container" );
516
525
}
517
526
527
+
528
+ /**
529
+ * Represents a state for {@link WebAsyncManager} to be in.
530
+ * <p><pre>
531
+ * NOT_STARTED <------+
532
+ * | |
533
+ * v |
534
+ * ASYNC_PROCESSING |
535
+ * | |
536
+ * v |
537
+ * RESULT_SET -------+
538
+ * </pre>
539
+ * @since 5.3.33
540
+ */
541
+ private enum State {
542
+
543
+ /** No async processing in progress. */
544
+ NOT_STARTED ,
545
+
546
+ /** Async handling has started, but the result hasn't been set yet. */
547
+ ASYNC_PROCESSING ,
548
+
549
+ /** The result is set, and an async dispatch was performed, unless there is a network error. */
550
+ RESULT_SET
551
+
552
+ }
553
+
518
554
}
0 commit comments