在Tornado中“超时”处理请求的正确方法


问题内容

我设法编写了一个相当愚蠢的错误,使我的一个请求处理程序运行非常慢的数据库查询。

有趣的是,我注意到,即使在很长的攻城战结束后,龙卷风仍在不断处理请求(有时是90年代后)。(评论->我不是100%知道《围攻》的运作方式,但是我很确定它关闭了连接。

我的问题分为两个部分:-当客户端关闭连接时,龙卷风是否会取消请求处理程序?-在Tornado中是否有超时请求处理程序的方法?

我通读了代码,似乎找不到任何东西。即使我的请求处理程序在上述错误中异步运行,未决请求的数量也会堆积到使应用程序变慢的水平,最好关闭连接。


问题答案:

当客户端断开连接时,Tornado不会自动关闭请求处理程序。但是,您可以重写on_connection_close以在客户端断开时收到警报,这将使您最终取消连接。上下文管理器(或装饰器)可用于处理设置超时以处理请求。用于tornado.ioloop.IOLoop.add_timeout安排一些方法,该方法会使请求timeout作为__enter__上下文管理器的一部分超时,然后__exit__在上下文管理器的块中取消该回调。这是一个演示这两个想法的示例:

import time
import contextlib

from tornado.ioloop import IOLoop
import tornado.web
from tornado import gen

@gen.coroutine
def async_sleep(timeout):
    yield gen.Task(IOLoop.instance().add_timeout, time.time() + timeout)

@contextlib.contextmanager
def auto_timeout(self, timeout=2): # Seconds
    handle = IOLoop.instance().add_timeout(time.time() + timeout, self.timed_out)
    try:
        yield handle
    except Exception as e:
        print("Caught %s" % e)
    finally:
        IOLoop.instance().remove_timeout(handle)
        if not self._timed_out:
            self.finish()
        else:
            raise Exception("Request timed out") # Don't continue on passed this point

class TimeoutableHandler(tornado.web.RequestHandler):
    def initialize(self):
        self._timed_out = False

    def timed_out(self):
        self._timed_out = True
        self.write("Request timed out!\n")
        self.finish()  # Connection to client closes here.
        # You might want to do other clean up here.

class MainHandler(TimeoutableHandler):

    @gen.coroutine
    def get(self):
        with auto_timeout(self): # We'll timeout after 2 seconds spent in this block.
            self.sleeper = async_sleep(5)
            yield self.sleeper
        print("writing")  # get will abort before we reach here if we timed out.
        self.write("hey\n")

    def on_connection_close(self):
        # This isn't the greatest way to cancel a future, since it will not actually
        # stop the work being done asynchronously. You'll need to cancel that some
        # other way. Should be pretty straightforward with a DB connection (close
        # the cursor/connection, maybe?)
        self.sleeper.set_exception(Exception("cancelled"))


application = tornado.web.Application([
    (r"/test", MainHandler),
])
application.listen(8888)
IOLoop.instance().start()