Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concurrency problem in DeferredResult: potential double execution of handleResult [SPR-14978] #19544

Closed
spring-projects-issues opened this issue Dec 5, 2016 · 6 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Dec 5, 2016

yan.zhang opened SPR-14978 and commented

!bug.jpg|thumbnail!

It looks like a concurrency bug.

"this.resultHandler.handleResult(this.result);" may be executed twice.

I think it shoule be:

    private boolean setResultInternal(Object result) {
        synchronized (this) {
            if (isSetOrExpired()) {
                return false;
            }
            this.result = result;

            if (this.resultHandler != null) {
                this.resultHandler.handleResult(this.result);
            }
        }
        return true;
    }

Affects: 3.2.17, 4.2.8, 4.3.4, 5.0 M3

Reference URL: https://github.com/spring-projects/spring-framework/blob/v5.0.0.M3/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java

Attachments:

Issue Links:

Backported to: 4.2.9, 3.2.18

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Dec 5, 2016

Juergen Hoeller commented

This looks like a side effect of the deadlock-avoiding fix for #15118: The code used to be the way you suggested it before but the overly strong lock for the handleResult callback led to a potential deadlock in Tomcat.

I guess we need to find a looser way of enforcing a single handleResult callback, e.g. using a volatile variable.

@spring-projects-issues
Copy link
Collaborator Author

yan.zhang commented

thx.

Sorry for my poor english.

I still hava some confusion about it.

It looks like the code in DeferredResult still will lead to a potential deadlock in Tomcat at present.

!exm1.jpg|thumbnail!
the code in DeferredResult.java
!exm2.png|thumbnail!
the code in WebAsyncManager.java
!exm3.jpg|thumbnail!
the code in DeferredResult.java

thread 1: (1)startAsyncProcessing                     (4)setResultHandler
thread 2:                                    (3)DeferredResult.setResult  
thread 3 (handle timeout):  (2)handleTimeout                   (5)setResultInternal

thread 1:setResultHandler(), holding deferredResult lock , requiring tomcat lock
thread 3:setResultInternal(), holding tomcat lock, requiring deferredResult lock

@Juergen Hoeller

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've revised DeferredResult to consistently make result handling decisions (including resultHandler != null checks) within its synchronized blocks but actually invoke handleResult outside, in both setResultInternal and setResultHandler. This should avoid any deadlock potential while still making sure that handleResult is only invoked by either setResultInternal or setResultHandler but never both.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Looks much better this way. I'm wondering if it's worth moving the timeout callback after setting the "timeout result" in a finally block. It is only a callback that can't influence or alter timeout handling. Either way a try-finally might be good to ensure setResultInternal is reached.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Hmm, onTimeout's documentation says: "It may invoke setResult or setErrorResult to resume processing." With the current setResultInternal call after the timeout callback invocation, we'd let that sort of custom result happen, with a timeoutResult effectively being ignored when any other result was set before... whereas if we turn it around, a specified timeoutResult will always win over any result the callback might set. So it looks like we'll rather go with a try-finally for the existing sequence?

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Oops, good point indeed that the callback can set the DeferredResult (even says so in onTimeout :)). So yes it needs to stay as it is with a try-finally for the existing sequence. One more point though that if setResulnternal(timeoutResult) does succeed the interceptor method should return false. There is no point to allow other interceptors down the chain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

2 participants