python - Tornado with_timeout 正确用法

标签 python tornado

我有一个运行一些 shell 命令的网络服务器。该命令通常需要几秒钟,但在某些情况下需要更多时间,在这种情况下客户端(不是网络浏览器或curl)会断开连接。

我无法修复客户端,所以我考虑修复服务器。它基于 Tornado 框架。我使用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()

这是curl 的输出:

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

我做错了什么?

最佳答案

虽然run_slow_commandcoroutine修饰,但它仍然处于阻塞状态,因此Tornado被阻塞并且无法运行任何代码,包括计时器,直到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 返回的 Future,因此根本不要使用 coroutine:

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

但是,您应该使用 Tornado 的 own Subprocess support,而不是使用 os.system 。总而言之,此示例等待子进程 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'))

关于python - Tornado with_timeout 正确用法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39251682/

相关文章:

python - Matplotlib 为一行多列创建表格数据

python - 使用与每个文件关联的编号而不是文件名上传文件

python - 用于下载数据的并行循环

python - 使用 Tornado RequestHandler 时最好的 REST 实现是什么

php - Apache/PHP 到 Nginx/Tornado/Python

python - Flask/Tornado 中的 Gzip 响应

python - 如何使 Django 管理 URL 仅供本地主机访问?

python - 如何检测双字节数字

python - 如何获取 Tornado 请求的客户端 IP?

python - Tornado Web 服务器和多线程共享问题