Skip to content

Commit

Permalink
Merge 5feef94 into 52185a5
Browse files Browse the repository at this point in the history
  • Loading branch information
ejeschke committed Oct 15, 2015
2 parents 52185a5 + 5feef94 commit 44aa17b
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 11 deletions.
20 changes: 20 additions & 0 deletions tests/test_timers.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,26 @@ def test_timer_comparison_coverage ():
time.sleep(.2)


def test_timer_time_remaining():
timer = Timer(timer_heap, 0, lambda: None)
assert timer == timer

timer.start(.1)
time.sleep(0.05)
remain = timer.remaining_time()
assert 0.047 < remain < 0.052


def test_timer_time_elapsed():
timer = Timer(timer_heap, 0, lambda: None)
assert timer == timer

timer.start(.1)
time.sleep(0.05)
elapsed = timer.elapsed_time()
assert 0.047 < elapsed < 0.052


def test_many_timers ():
"""Test more timers than standard thread based timer could handle"""
test_dict = {}
Expand Down
61 changes: 50 additions & 11 deletions timers/heap.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
__docformat__ = "restructuredtext en"


logger = logging.getLogger(__name__)
_logger = logging.getLogger(__name__)


@functools.total_ordering
Expand All @@ -39,12 +39,13 @@ def __init__ (self, heap, jitter, action, *args, **kwargs):
self.kwargs = kwargs
self.expire = None
self.timer_heap = heap
self.logger = heap.logger

def run (self):
try:
self.action(*self.args, **self.kwargs)
except Exception as ex:
logger.error("Ignoring uncaught exception within timer action: %s", str(ex))
self.logger.error("Ignoring uncaught exception within timer action: %s", str(ex))

def __hash__ (self):
return id(self)
Expand All @@ -61,26 +62,44 @@ def is_scheduled (self):
with self.timer_heap:
return self.expire is not None

def start (self, expire):
def start (self, time_sec):
self.stop()

self.expire = time.time()
self.start_time = time.time()
if self.jitter:
self.expire += expire * (1 - random.random() * self.jitter)
self.expire = self.start_time + time_sec * (
1 - random.random() * self.jitter)
else:
self.expire += expire
self.expire = self.start_time + time_sec

self.timer_heap.add(self)

def elapsed_time (self):
"""Return the elapsed time since the timer was started."""
return time.time() - self.start_time

def remaining_time (self):
"""Return the remaining time to the timer expiration.
If the timer has already expired then None is returned.
"""
if self.expire is None:
return 0.0
return self.expire - time.time()

def stop (self):
had_run = self.timer_heap.remove(self)
self.expire = None
return had_run


class TimerHeap (object):
def __init__ (self, desc):
def __init__ (self, desc='A Timer Heap', logger=None):
self.desc = desc
if logger is None:
# use module logger if user doesn't supply one
logger = _logger
self.logger = logger

self.timers = {}
self.heap = []
self.lock = threading.RLock()
Expand All @@ -89,13 +108,17 @@ def __init__ (self, desc):
self.expire_gen = 0

def __enter__ (self):
"Use with statement to hold lock"
"""Use with statement to hold lock"""
return self.lock.acquire()

def __exit__ (self, exc_type, exc_val, exc_tb):
"Use with statement to hold lock"
"""Use with statement to hold lock"""
return self.lock.release()

def timer (self, jitter, action, *args, **kwargs):
"""Convenience method to create a Timer from the heap"""
return Timer(self, jitter, action, *args, **kwargs)

def add (self, timer):
"""Add a timer to the heap"""
with self.lock:
Expand All @@ -111,7 +134,7 @@ def add (self, timer):
# Check to see if we need to reschedule our main timer.
# Only do this if we aren't expiring in the other thread.
if self.heap[0] != top and not self.expiring:
if self.rtimer:
if self.rtimer is not None:
self.rtimer.cancel()
# self.rtimer.join()
self.rtimer = None
Expand Down Expand Up @@ -154,7 +177,7 @@ def expire (self):
expired.expire = None
expired.run()
except Exception as ex:
logger.error("Unexpected Exception: %s", str(ex))
self.logger.error("Unexpected Exception: %s", str(ex))
finally:
# This is never set while expiring is True
assert self.rtimer is None
Expand Down Expand Up @@ -187,3 +210,19 @@ def remove (self, timer):
return False
else:
return True

def remove_all_timers (self):
"""Remove all waiting timers and terminate any blocking threads."""
with self.lock:
if self.rtimer is not None:
self.rtimer.cancel()

self.timers = {}
self.heap = []
self.rtimer = None
self.expiring = False

def quit (self):
"""Execute this method before your program exit to make sure it
doesn't block on waiting for a timer thread to terminate."""
self.remove_all_timers()

0 comments on commit 44aa17b

Please sign in to comment.