From d05e086b8dc475b1668b80f1b00572ca9489e7e2 Mon Sep 17 00:00:00 2001 From: Wenzhi Yu Date: Mon, 31 Oct 2016 16:25:48 +0800 Subject: [PATCH] Add FixedIntervalWithTimeoutLoopingCall Currently when using FixedIntervalLoopingCall, folks need to add timeout checking logic in their function if they need it. Adding a new class FixedIntervalWithTimeoutLoopingCall to provide timeout checking support will save those effort. Change-Id: I78bfb9e259c2394137d7efbc0ee96bb18a6dc5e7 --- oslo_service/loopingcall.py | 32 +++++++++++++++++++ oslo_service/tests/test_loopingcall.py | 9 ++++++ ...timeout-looping-call-5cc396b75597c3c2.yaml | 5 +++ 3 files changed, 46 insertions(+) create mode 100644 releasenotes/notes/add-timeout-looping-call-5cc396b75597c3c2.yaml diff --git a/oslo_service/loopingcall.py b/oslo_service/loopingcall.py index 72eaab1d..d535d171 100644 --- a/oslo_service/loopingcall.py +++ b/oslo_service/loopingcall.py @@ -17,6 +17,7 @@ import random import sys +import time from eventlet import event from eventlet import greenthread @@ -180,6 +181,37 @@ class FixedIntervalLoopingCall(LoopingCallBase): stop_on_exception=stop_on_exception) +class FixedIntervalWithTimeoutLoopingCall(LoopingCallBase): + """A fixed interval looping call with timeout checking mechanism.""" + + _RUN_ONLY_ONE_MESSAGE = _("A fixed interval looping call with timeout" + " checking and can only run one function at" + " at a time") + + _KIND = _('Fixed interval looping call with timeout checking.') + + def start(self, interval, initial_delay=None, + stop_on_exception=True, timeout=0): + start_time = time.time() + + def _idle_for(result, elapsed): + delay = round(elapsed - interval, 2) + if delay > 0: + func_name = reflection.get_callable_name(self.f) + LOG.warning(_LW('Function %(func_name)r run outlasted ' + 'interval by %(delay).2f sec'), + {'func_name': func_name, 'delay': delay}) + elapsed_time = time.time() - start_time + if timeout > 0 and elapsed_time > timeout: + raise LoopingCallTimeOut( + _('Looping call timed out after %.02f seconds') + % elapsed_time) + return -delay if delay < 0 else 0 + + return self._start(_idle_for, initial_delay=initial_delay, + stop_on_exception=stop_on_exception) + + class DynamicLoopingCall(LoopingCallBase): """A looping call which sleeps until the next known event. diff --git a/oslo_service/tests/test_loopingcall.py b/oslo_service/tests/test_loopingcall.py index c1495062..4a758639 100644 --- a/oslo_service/tests/test_loopingcall.py +++ b/oslo_service/tests/test_loopingcall.py @@ -180,6 +180,15 @@ class LoopingCallTestCase(test_base.BaseTestCase): (i, expected, actual)) self.assertAlmostEqual(expected, actual, message=message) + def test_looping_call_timed_out(self): + + def _fake_task(): + pass + + timer = loopingcall.FixedIntervalWithTimeoutLoopingCall(_fake_task) + self.assertRaises(loopingcall.LoopingCallTimeOut, + timer.start(interval=0.1, timeout=0.3).wait) + class DynamicLoopingCallTestCase(test_base.BaseTestCase): def setUp(self): diff --git a/releasenotes/notes/add-timeout-looping-call-5cc396b75597c3c2.yaml b/releasenotes/notes/add-timeout-looping-call-5cc396b75597c3c2.yaml new file mode 100644 index 00000000..5a7826ff --- /dev/null +++ b/releasenotes/notes/add-timeout-looping-call-5cc396b75597c3c2.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add a new type of looping call: FixedIntervalWithTimeoutLoopingCall. It is + a FixedIntervalLoopingCall with timeout checking.