mirror of
https://github.com/wassname/catalyst.git
synced 2026-09-12 12:12:04 +08:00
heartbeat and timeout usable as context managers
This commit is contained in:
+60
-37
@@ -1,84 +1,109 @@
|
|||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
class Timeout(Exception):
|
class Timeout(Exception):
|
||||||
|
|
||||||
def __init__(self, frame):
|
def __init__(self, frame, message=''):
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
class timeout(object):
|
class timeout(object):
|
||||||
"""
|
"""
|
||||||
Decorator to make a function raise TimeoutException if it spends
|
Utility to make a function raise TimeoutException if it spends
|
||||||
more than a specified number of seconds executing.
|
more than a specified number of seconds executing. Can be used
|
||||||
|
as a decorator to apply a static timeout to a function, or as
|
||||||
|
a context manager to dynamically add a timeout to a code block.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, seconds):
|
def __init__(self, seconds, message=''):
|
||||||
self.seconds = seconds
|
self.seconds = seconds
|
||||||
|
self.message = message
|
||||||
assert isinstance(seconds, Number), "Failed to specify a timeout."
|
assert isinstance(seconds, Number), "Failed to specify a timeout."
|
||||||
assert seconds > 0, "Timeout must be greater than 0"
|
assert seconds > 0, "Timeout must be greater than 0"
|
||||||
|
|
||||||
def handler(self, signum, frame):
|
def handler(self, signum, frame):
|
||||||
raise Timeout(frame)
|
raise Timeout(frame, self.message)
|
||||||
|
|
||||||
def __call__(self, fn):
|
def __call__(self, fn):
|
||||||
|
|
||||||
def wrapped(*args, **kwargs):
|
@wraps(fn)
|
||||||
|
def call_fn_with_timeout(*args, **kwargs):
|
||||||
# Set the alarm.
|
# Set the alarm.
|
||||||
signal.signal(signal.SIGALRM, self.handler)
|
signal.signal(signal.SIGALRM, self.handler)
|
||||||
signal.alarm(self.seconds)
|
signal.setitimer(signal.ITIMER_REAL, self.seconds, 0)
|
||||||
try:
|
try:
|
||||||
outval = fn(*args, **kwargs)
|
outval = fn(*args, **kwargs)
|
||||||
|
|
||||||
# Deactivate the alarm once we're done so that the
|
# Deactivate the alarm once we're done so that the
|
||||||
# decorator doesn't have unexpected side-effects later.
|
# decorator doesn't have unexpected side-effects later.
|
||||||
# Note that this will still raise TimeoutException if the
|
# Note that this will still raise Timeout if the
|
||||||
# call to fn takes too long.
|
# call to fn takes too long.
|
||||||
finally:
|
finally:
|
||||||
signal.alarm(0)
|
signal.setitimer(signal.ITIMER_REAL, 0, 0)
|
||||||
signal.signal(signal.SIGALRM, signal.SIG_DFL)
|
signal.signal(signal.SIGALRM, signal.SIG_DFL)
|
||||||
|
|
||||||
# Return the value of fn if it finished before the alarm. This
|
# Return the value of fn if it finished before the alarm. This
|
||||||
# won't execute if the Timeout was raised.
|
# won't execute if the Timeout was raised.
|
||||||
return outval
|
return outval
|
||||||
return wrapped
|
return call_fn_with_timeout
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
# Set the alarm on entrance.
|
||||||
|
signal.signal(signal.SIGALRM, self.handler)
|
||||||
|
signal.setitimer(signal.ITIMER_REAL, self.seconds, 0)
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
# Deactivate the alarm on exit. This will re-raise
|
||||||
|
# any exceptions raised inside the with block.
|
||||||
|
signal.signal(signal.SIGALRM, self.handler)
|
||||||
|
signal.setitimer(signal.ITIMER_REAL, self.seconds, 0)
|
||||||
|
|
||||||
class heartbeat(object):
|
class heartbeat(object):
|
||||||
"""
|
"""
|
||||||
Decorator to perform pseudo-heartbeat checks on a single-threaded
|
Utility to perform pseudo-heartbeat checks on a single-threaded
|
||||||
function. Calls frame_handler on the current stack frame of the
|
function. Calls frame_handler on the current stack frame of the
|
||||||
decorated function every ``interval`` seconds. After ``max_interval``
|
wrapped function every ``interval`` seconds. After ``max_interval``
|
||||||
intervals, raises MaxHeartBeats
|
intervals, raises Timeout. Can be used either as a decorator or
|
||||||
|
a context manager.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
interval,
|
||||||
|
max_intervals,
|
||||||
|
frame_handler=None,
|
||||||
|
timeout_message=''):
|
||||||
|
|
||||||
def __init__(self, interval, max_intervals, frame_handler=None):
|
|
||||||
self.count = 0
|
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.max_intervals = max_intervals
|
self.max_intervals = max_intervals
|
||||||
self.frame_handler = frame_handler
|
self.frame_handler = frame_handler
|
||||||
|
self.timeout_message = timeout_message
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
def handler(self, signum, frame):
|
def handler(self, signum, frame):
|
||||||
self.count += 1
|
self.count += 1
|
||||||
if self.frame_handler:
|
if self.frame_handler:
|
||||||
self.frame_handler(frame)
|
self.frame_handler(self.count, frame)
|
||||||
|
|
||||||
if self.count > self.max_intervals:
|
if self.count >= self.max_intervals:
|
||||||
raise Timeout(frame)
|
raise Timeout(frame, self.timeout_message)
|
||||||
|
|
||||||
def __call__(self, fn):
|
def __call__(self, fn):
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
# Set a timer to call our handler every N seconds.
|
@wraps(fn)
|
||||||
|
def call_fn_with_heartbeat(*args, **kwargs):
|
||||||
|
# Set a timer to call our handler every ``interval`` seconds.
|
||||||
signal.signal(signal.SIGALRM, self.handler)
|
signal.signal(signal.SIGALRM, self.handler)
|
||||||
signal.setitimer(signal.ITIMER_REAL, self.interval, self.interval)
|
signal.setitimer(signal.ITIMER_REAL, self.interval, self.interval)
|
||||||
try:
|
try:
|
||||||
outval = fn(*args, **kwargs)
|
outval = fn(*args, **kwargs)
|
||||||
|
|
||||||
# Deactivate the timer once we're done so that the
|
|
||||||
# decorator doesn't have unexpected side-effects later.
|
|
||||||
finally:
|
finally:
|
||||||
|
# Deactivate the timer once we're done so that the
|
||||||
|
# decorator doesn't have unexpected side-effects later.
|
||||||
signal.setitimer(signal.ITIMER_REAL, 0, 0)
|
signal.setitimer(signal.ITIMER_REAL, 0, 0)
|
||||||
signal.signal(signal.SIGALRM, signal.SIG_DFL)
|
signal.signal(signal.SIGALRM, signal.SIG_DFL)
|
||||||
|
|
||||||
@@ -86,17 +111,15 @@ class heartbeat(object):
|
|||||||
# an exception. This won't execute if the Timeout or any
|
# an exception. This won't execute if the Timeout or any
|
||||||
# other exception was raised by self.handle.
|
# other exception was raised by self.handle.
|
||||||
return outval
|
return outval
|
||||||
return wrapped
|
return call_fn_with_heartbeat
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import time
|
|
||||||
|
|
||||||
def pframe_g(frame):
|
def __enter__(self):
|
||||||
print frame.f_globals
|
# Set a timer to call our handler every N seconds.
|
||||||
|
signal.signal(signal.SIGALRM, self.handler)
|
||||||
@heartbeat(1, 10, pframe_g)
|
signal.setitimer(signal.ITIMER_REAL, self.interval, self.interval)
|
||||||
def foo():
|
|
||||||
for i in xrange(10000):
|
def __exit__(self, type, value, traceback):
|
||||||
time.sleep(.1)
|
# Turn off the timer on exit. This will re-raise any exception raised
|
||||||
print i
|
# during execution of the with-block
|
||||||
foo()
|
signal.setitimer(signal.ITIMER_REAL, 0, 0)
|
||||||
|
signal.signal(signal.SIGALRM, signal.SIG_DFL)
|
||||||
|
|||||||
Reference in New Issue
Block a user