2016-08-31 2 views
1

У меня есть веб-сервер, который запускает некоторую команду оболочки. Обычно команда занимает пару секунд, но в некоторых случаях требуется больше, и в этом случае клиент (это не веб-браузер или завиток) отключается.Tornado with_timeout правильное использование

У меня нет возможности исправить клиента, поэтому я подумал об исправлении сервера. Он основан на системе торнадо. Я переписал его с помощью функции tornado.gen.with_timeout, но по какой-то причине он не работает, как я ожидаю. Я устанавливаю тайм-аут до 5 секунд, поэтому при запросе сервера я ожидаю получить либо «законченный за X секунд» (где X < 5), либо «уже занял более 5 секунд ... все еще работает». И в обоих случаях я ожидаю получить ответ менее чем за 5 секунд.

Вот код:

import os 
import json 
import datetime 
import random 

from tornado.ioloop import IOLoop 
from tornado.web import RequestHandler, Application 
from tornado.gen import coroutine, with_timeout, TimeoutError, Return 

@coroutine 
def run_slow_command(command): 
    res = os.system(command) 
    raise Return(res) 


class MainHandler(RequestHandler): 
    @coroutine 
    def get(self): 
     TIMEOUT = 5 
     duration = random.randint(1, 15) 

     try: 
      yield with_timeout(datetime.timedelta(seconds=TIMEOUT), run_slow_command('sleep %d' % duration)) 
      response = {'status' : 'finished in %d seconds' % duration} 
     except TimeoutError: 
      response = {'status' : 'already took more than %d seconds... still running' % TIMEOUT} 

     self.set_header("Content-type", "application/json") 
     self.write(json.dumps(response) + '\n') 


def make_app(): 
    return Application([ 
     (r"/", MainHandler), 
    ]) 

if __name__ == "__main__": 
    app = make_app() 
    app.listen(8080) 
    IOLoop.current().start() 

А вот выход из завитка:

for i in `seq 1 5`; do curl http://127.0.0.1:8080/; done 
{"status": "finished in 15 seconds"} 
{"status": "finished in 12 seconds"} 
{"status": "finished in 3 seconds"} 
{"status": "finished in 11 seconds"} 
{"status": "finished in 13 seconds"} 

Что я делаю неправильно?

ответ

1

Хотя run_slow_command украшен coroutine он по-прежнему блокировки, так Торнадо заблокирован и не может запустить любой код, в том числе таймер, до завершения os.system вызова. Вы должны отложить вызов в теме:

from concurrent.futures import ThreadPoolExecutor 

thread_pool = ThreadPoolExecutor(4) 

@coroutine 
def run_slow_command(command): 
    res = yield thread_pool.submit(os.system, command) 
    raise Return(res) 

Или, так как вы просто хотите будущее, которая возвращенное submit, не используйте coroutine вообще:

def run_slow_command(command): 
    return thread_pool.submit(os.system, command) 

Вместо использования os.system, однако , вы должны использовать Tornado's own Subprocess support. Сложив все это вместе, этот пример ждет 5 секунд для подпроцесса, затем отключается:

from datetime import timedelta 
from functools import partial 

from tornado import gen 
from tornado.ioloop import IOLoop 
from tornado.process import Subprocess 

@gen.coroutine 
def run_slow_command(command): 
    yield gen.with_timeout(
     timedelta(seconds=5), 
     Subprocess(args=command.split()).wait_for_exit()) 

IOLoop.current().run_sync(partial(run_slow_command, 'sleep 10')) 
+1

Это сработало! Я полностью удалил командную строку 'run_slow_command' (в конце концов, это была только оболочка вокруг' os.system'), а в предложении try ... except я заменил свой исходный доход на 'yield with_timeout (datetime.timedelta (seconds = TIMEOUT), Subprocess (args = ('sleep% d'% duration) .split()). Wait_for_exit()) ' – Graf

 Смежные вопросы

  • Нет связанных вопросов^_^