Spring DeferredResult setResult timeout interaction

I am experimenting with Spring DeferredResult on Tomcat and I am getting crazy results. Is this what I'm doing wrong, or is there some kind of bug in Spring or Tomcat? My code is pretty simple.

 @Controller public class Test { private DeferredResult<String> deferred; static class DoSomethingUseful implements Runnable { public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { } } } @RequestMapping(value="/test/start") @ResponseBody public synchronized DeferredResult<String> start() { deferred = new DeferredResult<>(4000L, "timeout\n"); deferred.onTimeout(new DoSomethingUseful()); return deferred; } @RequestMapping(value="/test/stop") @ResponseBody public synchronized String stop() { deferred.setResult("stopped\n"); return "ok\n"; } } 

So. The start request creates a DeferredResult with a 4 second timeout. The stop request will set the result to DeferredResult . If you send stop before or after the expiration of the grace period, everything works fine.

However, if you send stop at the same time that the start time has expired, everything becomes crazy. I added the onTimeout action so that it is easy to reproduce, but this is not necessary for the problem to occur. With an APR connector, it simply locks. It sometimes works with the NIO connector, but sometimes it incorrectly sends a timeout message to the stop client and never answers the start client.

To check this:

 curl http://localhost/test/start & sleep 5; curl http://localhost/test/stop 

I do not think that I am doing something wrong. Spring's documentation seems to say that you can call setResult at any time, even after the request has already expired, and from any thread ("the application can produce the result from the stream of its choice").

Used versions: Tomcat 7.0.39 on Linux, Spring 3.2.2.

+4
source share
1 answer

This is a great search bug!
Just add additional error information ( which has been fixed) for a better understanding.

A synchronized block was set inside setResult (), which was extended to the send part of the send. This can cause a deadlock if the timeout occurs at the same time, because the Tomcat timeout thread has its own lock, which allows only one thread to timeout or send processing.

Detailed explanation:

When you call "stop" at the same time as requesting "time out", two threads try to block the DeferredResult object "delayed".

  • Thread executing the onTimeout handler Here is an excerpt from a Spring document:

    This onTimeout method is called from the container stream when the asynchronous request request expires before the DeferredResult parameter has been set. It can call setResult or setErrorResult to resume processing.

  • Another thread that runs the stop service.

If the send processing called during the stop () service receives a “pending” lock, it will wait for a tomcat lock (for example, TomcatLock) to complete the send.
And if another thread that is processing timeout has already acquired TomcatLock, that thread waits to get a “delayed” lock to complete setResult ()!

So, we are in a classic deadlock!

+3
source

All Articles