Python 如何在ContextDecorator中引发Tornado超时异常?

Python 如何在ContextDecorator中引发Tornado超时异常?,python,python-3.x,tornado,Python,Python 3.x,Tornado,我希望: 制作一个自定义计时器包装器,用于日志记录/其他计时目的 如果包装的操作超过预定义的时间长度,则包括抢先退出 这就是我到目前为止所做的: from contextlib import ContextDecorator import datetime from tornado import gen, ioloop from tornado.concurrent import Future class timing_logger(ContextDecorator): def

我希望:

  • 制作一个自定义计时器包装器,用于日志记录/其他计时目的
  • 如果包装的操作超过预定义的时间长度,则包括抢先退出
这就是我到目前为止所做的:

from contextlib import ContextDecorator
import datetime

from tornado import gen, ioloop
from tornado.concurrent import Future


class timing_logger(ContextDecorator):
    def __init__(self, allowed_ms):
        self.allowed_ms = allowed_ms
        self.f = Future()
        # this exception is not bubbled up by Tornado but fires
        gen.with_timeout(datetime.timedelta(seconds=1+allowed_ms/1000), self.f)

    def __enter__(self):
        self.start_time = datetime.datetime.now()
        return self

    def __exit__(self, exc_type, exc_val, traceback):
        self.f.set_result(True)
        elapsed_time_ms = (datetime.datetime.now() - self.start_time).total_seconds() * 1000

        if exc_type == gen.TimeoutError:
            raise TimeoutError('ruh oh, this is reeeally bad')

        if elapsed_time_ms > self.allowed_ms:
            raise TimeoutError('took {actual} ms, but was only allowed {allowed}.'.format(
                 actual=elapsed_time_ms, allowed=self.allowed_ms))

        else:
            print('worked. nothing to see here...')
        return False


@gen.coroutine
def main():

    with timing_logger(1000):
        # real application may be a variety of coroutines
        # and regular function calls, some of which may hang
        # for a long time
        for i in range(25):
            yield gen.sleep(0.1)


if __name__ == "__main__":
    ioloop.IOLoop.current().run_sync(
        lambda: main())
我在这里遇到的问题是,因为我没有生成具有_timeoutfuture的
gen

$python test.py 
ERROR:tornado.application:Future <tornado.concurrent.Future object at 0x10c7cb668> exception was never retrieved: tornado.gen.TimeoutError: Timeout
Traceback (most recent call last):
  File "test.py", line 48, in <module>
    lambda: main())
<snip>
    yielded = self.gen.send(value)
  File "test.py", line 43, in main
    yield gen.sleep(0.1)
  File "test.py", line 28, in __exit__
    actual=elapsed_time_ms, allowed=self.allowed_ms))
TimeoutError: took 2606.2940000000003 ms, but was only allowed 1000.
但我希望将上述内容包含到我的ContextDecorator中,因为必须将其复制到我想要使用的
计时记录器
的所有内容中,这将变得既乏味又容易出错

如何实现所需的功能以允许ContextDecorator将超时作为其功能的一部分


使用Python 3.6.1和最新的Tornado(4.5.1)。

您可以使用中断并触发此异常,而不是使用Tornado超时:

这将适当引发异常,从而生成完整的ContextDecorator,如下所示:

from contextlib import ContextDecorator
import datetime

from tornado import gen, ioloop
from tornado.concurrent import Future


class timing_logger(ContextDecorator):
    def __init__(self, allowed_ms):
        self.allowed_ms = allowed_ms
        self.f = Future()
        # this exception is not bubbled up by Tornado but fires
        gen.with_timeout(datetime.timedelta(seconds=1+allowed_ms/1000), self.f)

    def __enter__(self):
        self.start_time = datetime.datetime.now()
        def timeout_handler(signum, frame):
            raise gen.TimeoutError()  # could be any type of exception

        self.signal = signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(1 + self.allowed_ms // 1000)

        return self

    def __exit__(self, exc_type, exc_val, traceback):
        signal.alarm(0)
        self.f.set_result(True)
        elapsed_time_ms = (datetime.datetime.now() - self.start_time).total_seconds() * 1000

        if exc_type == gen.TimeoutError:
            raise TimeoutError('ruh oh, this is reeeally bad')

        if elapsed_time_ms > self.allowed_ms:
            raise TimeoutError('took {actual} ms, but was only allowed {allowed}.'.format(
                 actual=elapsed_time_ms, allowed=self.allowed_ms))

        else:
            print('worked. nothing to see here...')
        return False

请注意,您需要在
\uuuu退出\uuuu>中重置警报,否则它将在稍后的代码中触发。

您可以使用中断并触发此异常,而不是使用龙卷风超时:

这将适当引发异常,从而生成完整的ContextDecorator,如下所示:

from contextlib import ContextDecorator
import datetime

from tornado import gen, ioloop
from tornado.concurrent import Future


class timing_logger(ContextDecorator):
    def __init__(self, allowed_ms):
        self.allowed_ms = allowed_ms
        self.f = Future()
        # this exception is not bubbled up by Tornado but fires
        gen.with_timeout(datetime.timedelta(seconds=1+allowed_ms/1000), self.f)

    def __enter__(self):
        self.start_time = datetime.datetime.now()
        def timeout_handler(signum, frame):
            raise gen.TimeoutError()  # could be any type of exception

        self.signal = signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(1 + self.allowed_ms // 1000)

        return self

    def __exit__(self, exc_type, exc_val, traceback):
        signal.alarm(0)
        self.f.set_result(True)
        elapsed_time_ms = (datetime.datetime.now() - self.start_time).total_seconds() * 1000

        if exc_type == gen.TimeoutError:
            raise TimeoutError('ruh oh, this is reeeally bad')

        if elapsed_time_ms > self.allowed_ms:
            raise TimeoutError('took {actual} ms, but was only allowed {allowed}.'.format(
                 actual=elapsed_time_ms, allowed=self.allowed_ms))

        else:
            print('worked. nothing to see here...')
        return False
请注意,您需要重置
\uuuuu出口\uuuuu
中的警报,否则它将在稍后的代码中触发

from contextlib import ContextDecorator
import datetime

from tornado import gen, ioloop
from tornado.concurrent import Future


class timing_logger(ContextDecorator):
    def __init__(self, allowed_ms):
        self.allowed_ms = allowed_ms
        self.f = Future()
        # this exception is not bubbled up by Tornado but fires
        gen.with_timeout(datetime.timedelta(seconds=1+allowed_ms/1000), self.f)

    def __enter__(self):
        self.start_time = datetime.datetime.now()
        def timeout_handler(signum, frame):
            raise gen.TimeoutError()  # could be any type of exception

        self.signal = signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(1 + self.allowed_ms // 1000)

        return self

    def __exit__(self, exc_type, exc_val, traceback):
        signal.alarm(0)
        self.f.set_result(True)
        elapsed_time_ms = (datetime.datetime.now() - self.start_time).total_seconds() * 1000

        if exc_type == gen.TimeoutError:
            raise TimeoutError('ruh oh, this is reeeally bad')

        if elapsed_time_ms > self.allowed_ms:
            raise TimeoutError('took {actual} ms, but was only allowed {allowed}.'.format(
                 actual=elapsed_time_ms, allowed=self.allowed_ms))

        else:
            print('worked. nothing to see here...')
        return False