簡體   English   中英

如何在ContextDecorator中引發Tornado超時異常?

[英]How can I raise a Tornado timeout exception within a 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()
        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())

我這里的問題是,因為我沒有產生gen.with_timeout將來,所以在堆棧中我看到了:

$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.

龍卷風超時沒有被“冒泡”(因為缺少更好的單詞)。

我想讓__exit__捕獲異常,以便我可以處理它並適當地進行記錄,同時重新引發為其他異常類型。

我不確定是否需要:

  • 完全不使用ContextDecorator
  • 對我打龍卷風的方式/地點做一些不同的事情
  • ????

我知道在此示例中,我可以將所有調用代碼包裝到協程中,並在計時記錄器包裝周圍向main函數添加超時,如下所示:

@gen.coroutine
def main():


    @gen.coroutine
    def f():
        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)

    future = f()
    yield gen.with_timeout(datetime.timedelta(seconds=1), future)

但是我希望將以上內容包含到我的ContextDecorator中,因為不得不將其復制到我想使用timing_logger使用的所有內容周圍, timing_logger繁瑣又容易出錯。

如何實現所需的功能,以允許ContextDecorator包括超時作為其功能的一部分?

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

您可以使用信號來中斷並觸發此異常(而不是使用龍卷風超時)(盡管警報僅允許整數第二個輸入 ):

    def timeout_handler(signum, frame):
        raise gen.TimeoutError()

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

這將適當地引發異常,從而導致完整的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

請注意,您需要在__exit__重置警報,否則它將在以后的代碼中觸發。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM