diff --git a/neutron/openstack/common/periodic_task.py b/neutron/openstack/common/periodic_task.py index 1ebdc1af6..e1f5f78e0 100644 --- a/neutron/openstack/common/periodic_task.py +++ b/neutron/openstack/common/periodic_task.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +import random import time from oslo.config import cfg @@ -80,14 +81,14 @@ def periodic_task(*args, **kwargs): return f # NOTE(sirp): The `if` is necessary to allow the decorator to be used with - # and without parents. + # and without parenthesis. # - # In the 'with-parents' case (with kwargs present), this function needs to - # return a decorator function since the interpreter will invoke it like: + # In the 'with-parenthesis' case (with kwargs present), this function needs + # to return a decorator function since the interpreter will invoke it like: # # periodic_task(*args, **kwargs)(f) # - # In the 'without-parents' case, the original function will be passed + # In the 'without-parenthesis' case, the original function will be passed # in as the first argument, like: # # periodic_task(f) @@ -142,6 +143,27 @@ class _PeriodicTasksMeta(type): cls._periodic_spacing[name] = task._periodic_spacing +def _nearest_boundary(last_run, spacing): + """Find nearest boundary which is in the past, which is a multiple of the + spacing with the last run as an offset. + + Eg if last run was 10 and spacing was 7, the new last run could be: 17, 24, + 31, 38... + + 0% to 5% of the spacing value will be added to this value to ensure tasks + do not synchronize. This jitter is rounded to the nearest second, this + means that spacings smaller than 20 seconds will not have jitter. + """ + current_time = time.time() + if last_run is None: + return current_time + delta = current_time - last_run + offset = delta % spacing + # Add up to 5% jitter + jitter = int(spacing * (random.random() / 20)) + return current_time - offset + jitter + + @six.add_metaclass(_PeriodicTasksMeta) class PeriodicTasks(object): def __init__(self): @@ -159,17 +181,18 @@ class PeriodicTasks(object): spacing = self._periodic_spacing[task_name] last_run = self._periodic_last_run[task_name] - # If a periodic task is _nearly_ due, then we'll run it early + # Check if due, if not skip idle_for = min(idle_for, spacing) if last_run is not None: delta = last_run + spacing - time.time() - if delta > 0.2: + if delta > 0: idle_for = min(idle_for, delta) continue LOG.debug("Running periodic task %(full_task_name)s", {"full_task_name": full_task_name}) - self._periodic_last_run[task_name] = time.time() + self._periodic_last_run[task_name] = _nearest_boundary( + last_run, spacing) try: task(self, context)