Spring的DeferredResult setResult与超时交互

时间:2013-04-19 12:47:56

标签: java spring tomcat comet spring-3

我在Tomcat上尝试使用Spring的DeferredResult,我得到了疯狂的结果。是我做错了,还是Spring或Tomcat中有一些错误?我的代码很简单。

@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";
    }
}

因此。 start请求创建DeferredResult,超时为4秒。 stop请求会在DeferredResult上设置结果。如果您在延迟结果超时之前或之后发送stop,一切正常。

但是,如果您在stop次的同时发送start,事情就会变得疯狂。我添加了一个onTimeout动作,以便于重现,但这不是问题发生的必要条件。使用APR连接器,它只是死锁。使用NIO连接器时,它有时会起作用,但有时会错误地将“超时”消息发送到stop客户端,并且永远不会回答start客户端。

测试一下:

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

我认为我做错了什么。 Spring文档似乎说可以随时调用setResult,即使在请求已经过期之后,也可以从任何线程(“  应用程序可以从其选择的线程中生成结果。)。

使用的版本:Linux上的Tomcat 7.0.39,Spring 3.2.2。

1 个答案:

答案 0 :(得分:3)

这是一个很好的bug发现!
只需添加有关错误的更多信息(that got fixed),以便更好地理解。

setResult()中有一个synchronized块,它扩展到提交调度的部分。如果由于Tomcat超时线程有自己的锁定只允许一个线程执行超时或分派处理,则如果同时发生超时,则可能导致死锁。

详细说明:

当你在请求“超时”的同时调用“stop”时,两个线程试图锁定DeferredResult对象'deferred'。

  1. 执行“onTimeout”处理程序的线程 以下是Spring doc的摘录:

      

    当异步请求在设置DeferredResult之前超时时,从容器线程调用此onTimeout方法。它可以调用 setResult 或setErrorResult来恢复处理。

  2. 执行“停止”服务的另一个线程。

  3. 如果在stop()服务期间调用的调度处理获得'deferred'锁,它将等待tomcat锁(比如TomcatLock)完成调度。
    如果执行超时处理的其他线程已经获取了TomcatLock,那么该线程等待获取'deferred'的锁来完成setResult()!

    所以,我们最终处于经典的僵局状态!