Может ли Tornado RequestHandler обрабатывать запросы, ожидая завершения Future?

Может ли один класс Tornado RequestHandler принимать новые запросы, ожидая завершения Future в одном из своих экземпляров?

Я отлаживал сопрограмму Tornado, которая вызывала ThreadPoolExecutor, и заметил, что пока сопрограмма ждала завершения исполнителя, RequestHandler была заблокирована. Таким образом, любые новые запросы к этому обработчику ожидают завершения сопрограммы.

Вот код, который я написал, чтобы воспроизвести свое наблюдение:

from time import sleep
from concurrent.futures import ThreadPoolExecutor
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.web import Application, RequestHandler
from tornado.gen import coroutine

class Handler1(RequestHandler):
    @coroutine
    def get(self):
        print('Setting up executor ...')
        thread_pool = ThreadPoolExecutor(1)

        print('Yielding ...')
        yield thread_pool.submit(sleep, 30)

        self.write('Ready!')
        print('Finished!')

app = Application([('/1$', Handler1)])
app.listen(8888)
PeriodicCallback(lambda: print('##'), 10000).start()
IOLoop.instance().start()

Теперь, если я дважды обращаюсь к localhost:8888/1, я получаю следующий вывод:

##
Setting up executor ...
Yielding ...
##
##
##
Finished!
Setting up executor ...
Yielding ...
##
##
##
Finished!
##

Но я ожидаю, что произойдет следующее:

##
Setting up executor ...
Yielding ...
Setting up executor ...
Yielding ...
##
##
##
Finished!
Finished!
##

Обратите внимание, что блокируется только RequestHandler, потому что мы по-прежнему получаем ## каждые 10 секунд. На самом деле, если вы добавите еще один идентичный RequestHandler (Handler2) и получите доступ к localhost:8888/1 и localhost:8888/2, это приведет к ожидаемому результату.

Это нормально? Это предполагаемое поведение?

Извините за мой плохой английский.


person Cristóbal Ganter    schedule 28.02.2015    source источник


Ответы (2)


Tornado создает новый экземпляр вашего RequestHandler для каждого нового запроса. Итак, ваш код действительно ведет себя так, как вы ожидаете. Я запускаю его и открываю два окна терминала, в каждом из которых работает wget localhost:8888/1. Ваш код печатает:

Setting up executor ...
Yielding ...
Setting up executor ...
Yielding ...
##
##
##
Finished!
Finished!

Как вы ожидаете. Вероятно, вы заметили, что ваш браузер не желает одновременно открывать два соединения с одним и тем же URL-адресом. Действительно, я могу воспроизвести «блокирующее» поведение, которое вы видите в хроме, если я открою две вкладки и попытаюсь загрузить «localhost: 8888/1» в обеих. Однако, если я изменю ваш код:

app = Application([
    ('/1$', Handler1),
    ('/2$', Handler1)])

И откройте «localhost: 8888/1» и «localhost: 8888/2» на двух вкладках в Chrome, я вижу, что он открывает оба соединения одновременно.

Попробуйте wget для проверки без вмешательства браузера.

person A. Jesse Jiryu Davis    schedule 28.02.2015
comment
Огромное спасибо! Я уже начал думать, что я сошел с ума. Теперь все имеет смысл! Я не знал об этой функции в браузерах. - person Cristóbal Ganter; 01.03.2015

Также работает для меня:

$ curl http://127.0.0.1:8888/1 &
[1] 95055
$ curl http://127.0.0.1:8888/1 &
[2] 95056
$ curl http://127.0.0.1:8888/1 &
[3] 95057

Вывод программы Торнадо:

bash-3.2$ python3 test.py 
##
##
Setting up executor ...
Yielding ...
Setting up executor ...
Yielding ...
Setting up executor ...
Yielding ...
##
##
##
Finished!
Finished!
Finished!
##
##
##
person Vovan Kuznetsov    schedule 28.02.2015