【问题标题】:Spring's DeferredResult setResult interaction with timeoutsSpring 的 DeferredResult setResult 与超时的交互
【发布时间】:2013-04-19 12:47:55
【问题描述】:

我在 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 请求创建一个具有 4 秒超时的 DeferredResultstop 请求将在DeferredResult 上设置结果。如果您在延迟结果超时之前或之后发送stop,一切正常。

但是,如果您在 start 超时的同时发送 stop,事情就会变得疯狂。我添加了一个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。

【问题讨论】:

  • 当 DeferredResult 被认为是根据文档提供异步响应的替代方法时,对方法上的同步有点警惕。 static.springsource.org/spring/docs/3.2.0.BUILD-SNAPSHOT/api/… 告诉一个方法它是同步的假定同步行为,如果不小心可能会导致死锁。
  • synchronizeds 是我原始代码的残余。可以肯定的是,我删除了它们并重新测试。它没有改变任何东西。
  • 我在上游报告了这个问题,这个 bug 已经在 Spring 3.2.3 中修复了。

标签: java spring tomcat comet spring-3


【解决方案1】:

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

在 setResult() 中有一个同步块,它一直延伸到提交分派的部分。如果同时发生超时,这可能会导致死锁,因为 Tomcat 超时线程有自己的锁定,只允许一个线程进行超时或分派处理。

详细解释:

当您在请求“超时”的同时调用“停止”时,两个线程正在尝试锁定 DeferredResult 对象“延迟”。

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

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

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

如果在 stop() 服务期间调用的调度处理获得了“延迟”锁,它将等待一个 tomcat 锁(比如 TomcatLock)来完成调度。
并且如果另一个执行超时处理的线程已经获取了 TomcatLock,那么该线程将等待获取 'deferred' 上的锁以完成 setResult()!

所以,我们最终陷入了典型的僵局!

【讨论】:

    猜你喜欢
    • 2013-01-21
    • 2014-11-10
    • 1970-01-01
    • 1970-01-01
    • 2019-06-05
    • 1970-01-01
    • 1970-01-01
    • 2011-07-22
    • 2012-12-12
    相关资源
    最近更新 更多