Skip to content

Commit e2ea87f

Browse files
committed
Merge branch '6.2.x'
2 parents 8bba4f6 + a985b73 commit e2ea87f

File tree

4 files changed

+138
-100
lines changed

4 files changed

+138
-100
lines changed

spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java

+58-48
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -280,65 +280,75 @@ public boolean setErrorResult(Object result) {
280280
}
281281

282282

283-
final DeferredResultProcessingInterceptor getInterceptor() {
284-
return new DeferredResultProcessingInterceptor() {
285-
@Override
286-
public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
287-
boolean continueProcessing = true;
288-
try {
289-
if (timeoutCallback != null) {
290-
timeoutCallback.run();
291-
}
292-
}
293-
finally {
294-
Object value = timeoutResult.get();
295-
if (value != RESULT_NONE) {
296-
continueProcessing = false;
297-
try {
298-
setResultInternal(value);
299-
}
300-
catch (Throwable ex) {
301-
logger.debug("Failed to handle timeout result", ex);
302-
}
303-
}
283+
final DeferredResultProcessingInterceptor getLifecycleInterceptor() {
284+
return new LifecycleInterceptor();
285+
}
286+
287+
288+
/**
289+
* Handles a DeferredResult value when set.
290+
*/
291+
@FunctionalInterface
292+
public interface DeferredResultHandler {
293+
294+
void handleResult(@Nullable Object result);
295+
}
296+
297+
298+
/**
299+
* Instance interceptor to receive Servlet container notifications.
300+
*/
301+
private class LifecycleInterceptor implements DeferredResultProcessingInterceptor {
302+
303+
@Override
304+
public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> result) {
305+
boolean continueProcessing = true;
306+
try {
307+
if (timeoutCallback != null) {
308+
timeoutCallback.run();
304309
}
305-
return continueProcessing;
306310
}
307-
@Override
308-
public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> deferredResult, Throwable t) {
309-
try {
310-
if (errorCallback != null) {
311-
errorCallback.accept(t);
312-
}
313-
}
314-
finally {
311+
finally {
312+
Object value = timeoutResult.get();
313+
if (value != RESULT_NONE) {
314+
continueProcessing = false;
315315
try {
316-
setResultInternal(t);
316+
setResultInternal(value);
317317
}
318318
catch (Throwable ex) {
319-
logger.debug("Failed to handle error result", ex);
319+
logger.debug("Failed to handle timeout result", ex);
320320
}
321321
}
322-
return false;
323322
}
324-
@Override
325-
public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
326-
expired = true;
327-
if (completionCallback != null) {
328-
completionCallback.run();
323+
return continueProcessing;
324+
}
325+
326+
@Override
327+
public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> result, Throwable t) {
328+
try {
329+
if (errorCallback != null) {
330+
errorCallback.accept(t);
329331
}
330332
}
331-
};
332-
}
333-
333+
finally {
334+
try {
335+
setResultInternal(t);
336+
}
337+
catch (Throwable ex) {
338+
logger.debug("Failed to handle error result", ex);
339+
}
340+
}
341+
return false;
342+
}
334343

335-
/**
336-
* Handles a DeferredResult value when set.
337-
*/
338-
@FunctionalInterface
339-
public interface DeferredResultHandler {
344+
@Override
345+
public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> result) {
346+
expired = true;
347+
if (completionCallback != null) {
348+
completionCallback.run();
349+
}
350+
}
340351

341-
void handleResult(@Nullable Object result);
342352
}
343353

344354
}

spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java

+47-40
Original file line numberDiff line numberDiff line change
@@ -375,36 +375,6 @@ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object..
375375
}
376376
}
377377

378-
private void setConcurrentResultAndDispatch(@Nullable Object result) {
379-
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
380-
synchronized (WebAsyncManager.this) {
381-
if (!this.state.compareAndSet(State.ASYNC_PROCESSING, State.RESULT_SET)) {
382-
if (logger.isDebugEnabled()) {
383-
logger.debug("Async result already set: [" + this.state.get() +
384-
"], ignored result for " + formatUri(this.asyncWebRequest));
385-
}
386-
return;
387-
}
388-
389-
this.concurrentResult = result;
390-
if (logger.isDebugEnabled()) {
391-
logger.debug("Async result set for " + formatUri(this.asyncWebRequest));
392-
}
393-
394-
if (this.asyncWebRequest.isAsyncComplete()) {
395-
if (logger.isDebugEnabled()) {
396-
logger.debug("Async request already completed for " + formatUri(this.asyncWebRequest));
397-
}
398-
return;
399-
}
400-
401-
if (logger.isDebugEnabled()) {
402-
logger.debug("Performing async dispatch for " + formatUri(this.asyncWebRequest));
403-
}
404-
this.asyncWebRequest.dispatch();
405-
}
406-
}
407-
408378
/**
409379
* Start concurrent request processing and initialize the given
410380
* {@link DeferredResult} with a {@link DeferredResultHandler} that saves
@@ -437,7 +407,7 @@ public void startDeferredResultProcessing(
437407
}
438408

439409
List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<>();
440-
interceptors.add(deferredResult.getInterceptor());
410+
interceptors.add(deferredResult.getLifecycleInterceptor());
441411
interceptors.addAll(this.deferredResultInterceptors.values());
442412
interceptors.add(timeoutDeferredResultInterceptor);
443413

@@ -449,6 +419,11 @@ public void startDeferredResultProcessing(
449419
}
450420
try {
451421
interceptorChain.triggerAfterTimeout(this.asyncWebRequest, deferredResult);
422+
synchronized (WebAsyncManager.this) {
423+
// If application thread set the DeferredResult first in a race,
424+
// we must still not return until setConcurrentResultAndDispatch is done
425+
return;
426+
}
452427
}
453428
catch (Throwable ex) {
454429
setConcurrentResultAndDispatch(ex);
@@ -460,10 +435,12 @@ public void startDeferredResultProcessing(
460435
logger.debug("Servlet container error notification for " + formatUri(this.asyncWebRequest));
461436
}
462437
try {
463-
if (!interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex)) {
438+
interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex);
439+
synchronized (WebAsyncManager.this) {
440+
// If application thread set the DeferredResult first in a race,
441+
// we must still not return until setConcurrentResultAndDispatch is done
464442
return;
465443
}
466-
deferredResult.setErrorResult(ex);
467444
}
468445
catch (Throwable interceptorEx) {
469446
setConcurrentResultAndDispatch(interceptorEx);
@@ -502,6 +479,36 @@ private void startAsyncProcessing(Object[] processingContext) {
502479
this.asyncWebRequest.startAsync();
503480
}
504481

482+
private void setConcurrentResultAndDispatch(@Nullable Object result) {
483+
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
484+
synchronized (WebAsyncManager.this) {
485+
if (!this.state.compareAndSet(State.ASYNC_PROCESSING, State.RESULT_SET)) {
486+
if (logger.isDebugEnabled()) {
487+
logger.debug("Async result already set: [" + this.state.get() + "], " +
488+
"ignored result for " + formatUri(this.asyncWebRequest));
489+
}
490+
return;
491+
}
492+
493+
this.concurrentResult = result;
494+
if (logger.isDebugEnabled()) {
495+
logger.debug("Async result set for " + formatUri(this.asyncWebRequest));
496+
}
497+
498+
if (this.asyncWebRequest.isAsyncComplete()) {
499+
if (logger.isDebugEnabled()) {
500+
logger.debug("Async request already completed for " + formatUri(this.asyncWebRequest));
501+
}
502+
return;
503+
}
504+
505+
if (logger.isDebugEnabled()) {
506+
logger.debug("Performing async dispatch for " + formatUri(this.asyncWebRequest));
507+
}
508+
this.asyncWebRequest.dispatch();
509+
}
510+
}
511+
505512
private static String formatUri(AsyncWebRequest asyncWebRequest) {
506513
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
507514
return (request != null ? "\"" + request.getRequestURI() + "\"" : "servlet container");
@@ -511,13 +518,13 @@ private static String formatUri(AsyncWebRequest asyncWebRequest) {
511518
/**
512519
* Represents a state for {@link WebAsyncManager} to be in.
513520
* <p><pre>
514-
* NOT_STARTED <------+
515-
* | |
516-
* v |
517-
* ASYNC_PROCESSING |
518-
* | |
519-
* v |
520-
* RESULT_SET -------+
521+
* +------> NOT_STARTED <------+
522+
* | | |
523+
* | v |
524+
* | ASYNC_PROCESSING |
525+
* | | |
526+
* | v |
527+
* <-------+ RESULT_SET -------+
521528
* </pre>
522529
* @since 5.3.33
523530
*/

spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -93,7 +93,7 @@ void onCompletion() throws Exception {
9393
DeferredResult<String> result = new DeferredResult<>();
9494
result.onCompletion(() -> sb.append("completion event"));
9595

96-
result.getInterceptor().afterCompletion(null, null);
96+
result.getLifecycleInterceptor().afterCompletion(null, null);
9797

9898
assertThat(result.isSetOrExpired()).isTrue();
9999
assertThat(sb.toString()).isEqualTo("completion event");
@@ -109,7 +109,7 @@ void onTimeout() throws Exception {
109109
result.setResultHandler(handler);
110110
result.onTimeout(() -> sb.append("timeout event"));
111111

112-
result.getInterceptor().handleTimeout(null, null);
112+
result.getLifecycleInterceptor().handleTimeout(null, null);
113113

114114
assertThat(sb.toString()).isEqualTo("timeout event");
115115
assertThat(result.setResult("hello")).as("Should not be able to set result a second time").isFalse();
@@ -127,7 +127,7 @@ void onError() throws Exception {
127127
Exception e = new Exception();
128128
result.onError(t -> sb.append("error event"));
129129

130-
result.getInterceptor().handleError(null, null, e);
130+
result.getLifecycleInterceptor().handleError(null, null, e);
131131

132132
assertThat(sb.toString()).isEqualTo("error event");
133133
assertThat(result.setResult("hello")).as("Should not be able to set result a second time").isFalse();

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ReactiveTypeHandler.java

+29-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -350,11 +350,18 @@ public void run() {
350350
this.subscription.request(1);
351351
}
352352
catch (final Throwable ex) {
353-
if (logger.isTraceEnabled()) {
354-
logger.trace("Send for " + this.emitter + " failed: " + ex);
353+
if (logger.isDebugEnabled()) {
354+
logger.debug("Send for " + this.emitter + " failed: " + ex);
355355
}
356356
terminate();
357-
this.emitter.completeWithError(ex);
357+
try {
358+
this.emitter.completeWithError(ex);
359+
}
360+
catch (Exception ex2) {
361+
if (logger.isDebugEnabled()) {
362+
logger.debug("Failure from emitter completeWithError: " + ex2);
363+
}
364+
}
358365
return;
359366
}
360367
}
@@ -364,16 +371,30 @@ public void run() {
364371
Throwable ex = this.error;
365372
this.error = null;
366373
if (ex != null) {
367-
if (logger.isTraceEnabled()) {
368-
logger.trace("Publisher for " + this.emitter + " failed: " + ex);
374+
if (logger.isDebugEnabled()) {
375+
logger.debug("Publisher for " + this.emitter + " failed: " + ex);
376+
}
377+
try {
378+
this.emitter.completeWithError(ex);
379+
}
380+
catch (Exception ex2) {
381+
if (logger.isDebugEnabled()) {
382+
logger.debug("Failure from emitter completeWithError: " + ex2);
383+
}
369384
}
370-
this.emitter.completeWithError(ex);
371385
}
372386
else {
373387
if (logger.isTraceEnabled()) {
374388
logger.trace("Publisher for " + this.emitter + " completed");
375389
}
376-
this.emitter.complete();
390+
try {
391+
this.emitter.complete();
392+
}
393+
catch (Exception ex2) {
394+
if (logger.isDebugEnabled()) {
395+
logger.debug("Failure from emitter complete: " + ex2);
396+
}
397+
}
377398
}
378399
return;
379400
}

0 commit comments

Comments
 (0)