2013-04-19 4 views
4

Я экспериментирую с весной DeferredResult на Tomcat, и я получаю сумасшедшие результаты. Это то, что я делаю неправильно, или есть какая-то ошибка весной или Tomcat? Мой код достаточно прост.Spring's DeferredResult setResult-взаимодействие с таймаутами

@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. Запрос start создает DeferredResult с таймаутом 4 секунды. Запрос stop установит результат на DeferredResult. Если вы отправляете stop до или после истечения срока отсрочки, все работает нормально.

Однако, если вы пришлете stop в то же время, что и start раз, все сойдет с ума. Я добавил действие onTimeout, чтобы это было легко воспроизвести, но это не обязательно для возникновения проблемы. С разъемом APR он просто блокируется. С разъемом NIO он иногда работает, но иногда он неправильно отправляет сообщение «timeout» клиенту stop и никогда не отвечает клиенту start.

Чтобы проверить это:

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

Я не думаю, что я делаю что-то неправильно. Документация Spring, похоже, говорит, что в любой момент можно позвонить setResult даже после того, как запрос уже истек, и из любого потока («приложение может произвести результат из потока по своему выбору»).

Используемые версии: Tomcat 7.0.39 на Linux, Spring 3.2.2.

+0

Опасность синхронности методов, когда DeferredResult считается альтернативным способом предоставления асинхронных ответов в соответствии с документацией. http://static.springsource.org/spring/docs/3.2.0.BUILD-SNAPSHOT/api/org/springframework/web/context/request/async/DeferredResult.html Передача синхронного метода предполагает синхронное поведение и может привести к тупиковой ситуации, если не быть осторожным. – dardo

+0

«synchronized's» - это остаток моего исходного кода. Чтобы быть уверенным, я удалил их и перепроверил. Это ничего не изменило. – pdw

+0

Я сообщил об этом выше по течению, и эта ошибка была исправлена ​​весной 3.2.3. – pdw

ответ

3

Это отличная ошибка поиска!
Просто добавьте дополнительную информацию об ошибке (that got fixed) для лучшего понимания.

Был установлен синхронизированный блок внутри setResult(), который был расширен до части отправки отправки. Это может вызвать тупик, если тайм-аут происходит одновременно, поскольку поток таймаута Tomcat имеет свою собственную блокировку, которая разрешает только одному потоку выполнять тайм-аут или обработку отправки.

Подробное описание:

При вызове «стоп» в то же время, как запрос «время ожидания», два потока пытается заблокировать объект DeferredResult «отложенные».

  1. поток, который выполняет «onTimeout» обработчик Вот выдержка из Spring док:

    Этот onTimeout метод вызывается из контейнера нити, когда запрос асинхронной раз из перед DeferredResult был установлен. Он может вызывать setResult или setErrorResult для возобновления обработки.

  2. Другая тема, которая выполняет «стоп-сервис».

Если обработка отправки вызывается во время службы остановки() получает «отложенный» замок, он будет ждать TOMCAT замка (скажем TomcatLock), чтобы закончить отправку.
И если другой поток, выполняющий обработку тайм-аута, уже приобрел TomcatLock, этот поток ждет, чтобы получить блокировку «отложен» для завершения setResult()!

Итак, мы закончили классическую тупиковую ситуацию!