Skip to content

Commit

Permalink
Merge pull request #204 from onefinestay/saner_timer
Browse files Browse the repository at this point in the history
simpler timer implementation
  • Loading branch information
mattbennett committed Feb 16, 2015
2 parents 72e4dfc + a1df1be commit 62f6936
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 63 deletions.
50 changes: 17 additions & 33 deletions nameko/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@


class Timer(Entrypoint):
def __init__(self, interval=None, config_key=None):
def __init__(self, interval):
"""
Timer entrypoint implementation. Fires every :attr:`self.interval`
seconds.
Either `interval` or `config_key` must be provided. If given,
`config_key` specifies a key in the config will be used as the
interval.
The implementation sleeps first, i.e. does not fire at time 0.
Example::
Expand All @@ -28,25 +26,15 @@ class Service(object):
@timer(interval=5)
def tick(self):
self.shrub(body)
pass
@timer(config_key='tock_interval')
def tock(self):
self.shrub(body)
"""
self._default_interval = interval
self.config_key = config_key
self.interval = interval
self.should_stop = Event()
self.gt = None

def start(self):
_log.debug('starting %s', self)
interval = self._default_interval

if self.config_key:
interval = self.container.config.get(self.config_key, interval)

self.interval = interval
self.gt = self.container.spawn_managed_thread(self._run)

def stop(self):
Expand All @@ -59,30 +47,26 @@ def kill(self):
self.gt.kill()

def _run(self):
''' Runs the interval loop.
""" Runs the interval loop. """

sleep_time = self.interval

while True:
# sleep for `sleep_time`, unless `should_stop` fires, in which
# case we leave the while loop and stop entirely
with Timeout(sleep_time, exception=False):
self.should_stop.wait()
break

This should not be called directly, rather the `start()` method
should be used.
'''
while not self.should_stop.ready():
start = time.time()

self.handle_timer_tick()

elapsed_time = (time.time() - start)

# next time, sleep however long is left of our interval, taking
# off the time we took to run
sleep_time = max(self.interval - elapsed_time, 0)
self._sleep_or_stop(sleep_time)

def _sleep_or_stop(self, sleep_time):
''' Sleeps for `sleep_time` seconds or until a `should_stop` event
has been fired, whichever comes first.
'''
try:
with Timeout(sleep_time):
self.should_stop.wait()
except Timeout:
# we use the timeout as a cancellable sleep
pass

def handle_timer_tick(self):
args = ()
Expand Down
32 changes: 2 additions & 30 deletions test/test_timers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
def test_provider():
container = Mock(spec=ServiceContainer)
container.service_name = "service"
container.config = Mock()
container.spawn_managed_thread = eventlet.spawn

timer = Timer(interval=0).bind(container, "method")
timer = Timer(interval=0.1).bind(container, "method")
timer.setup()
timer.start()

assert timer.interval == 0
assert timer.interval == 0.1

with wait_for_call(1, container.spawn_worker) as spawn_worker:
with Timeout(1):
Expand All @@ -30,33 +29,6 @@ def test_provider():
assert timer.gt.dead


def test_provider_uses_config_for_interval():
container = Mock(spec=ServiceContainer)
container.service_name = "service"
container.config = {'spam-conf': 10}
container.spawn_managed_thread = eventlet.spawn

timer = Timer(config_key='spam-conf').bind(container, "method")
timer.setup()
timer.start()

assert timer.interval == 10
timer.stop()


def test_provider_interval_as_config_fallback():
container = Mock(spec=ServiceContainer)
container.service_name = "service"
container.config = {}

timer = Timer(interval=1, config_key='spam-conf').bind(container, "method")
timer.setup()
timer.start()

assert timer.interval == 1
timer.stop()


def test_stop_timer_immediatly():
container = Mock(spec=ServiceContainer)
container.service_name = "service"
Expand Down

0 comments on commit 62f6936

Please sign in to comment.