【问题标题】:Right way to "timeout" a Request in Tornado在 Tornado 中“超时”请求的正确方法
【发布时间】:2014-08-15 13:39:35
【问题描述】:

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

有趣的是,我注意到即使在围攻完成很久之后 Tornado 仍然在不断地收到请求(有时是 90 年代之后)。 (评论 --> 我不是 100% 确定 Siege 的工作原理,但我相当确定它关闭了连接..)

我的问题分为两部分: - 当客户端关闭连接时,Tornado 是否取消请求处理程序? - 有没有办法让 Tornado 中的请求处理程序超时?

我通读了代码,但似乎找不到任何东西。尽管我的请求处理程序在上述错误中异步运行,但挂起的请求数量堆积到了应用程序速度变慢的水平,最好关闭连接。

【问题讨论】:

    标签: python tornado


    【解决方案1】:

    当客户端断开连接时,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()
    

    【讨论】:

    • 知道我为什么会遇到这个问题:TypeError: object generator can't be used in 'await' expression
    • @lateautumntear 这个答案早于 async/await 语法,因此它可能无法在新版本的龙卷风中按原样工作。可能您需要使用异步方法而不是用@gen.coroutine 装饰的方法,并且可能用等待替换产量?
    【解决方案2】:

    解决这个问题的另一种方法是使用gen.with_timeout:

    import time
    from tornado import gen
    from tornado.util import TimeoutError
    
    
    class MainHandler
    
        @gen.coroutine
        def get(self):
            try:
                # I'm using gen.sleep here but you can use any future in this place
                yield gen.with_timeout(time.time() + 2, gen.sleep(5))
                self.write("This will never be reached!!")
            except TimeoutError as te:
                logger.warning(te.__repr__())
                self.timed_out()
    
        def timed_out(self):
            self.write("Request timed out!\n")
    

    我喜欢 contextlib 解决方案的处理方式,但我总是得到日志记录。

    原生协程解决方案是:

    async def get(self):
        try:
            await gen.with_timeout(time.time() + 2, gen.sleep(5))
            self.write("This will never be reached!!")
        except TimeoutError as te:
            logger.warning(te.__repr__())
            self.timed_out()
    

    【讨论】:

      猜你喜欢
      • 2021-10-07
      • 1970-01-01
      • 2023-03-20
      • 2014-03-02
      • 2010-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多