From 53d0dabd2cb525dfea7a0689745c22a7687f5e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 10 Jan 2021 00:18:24 +0200 Subject: [PATCH] Ensured that jitter is always positive (#416) Fixes #291. --- apscheduler/triggers/base.py | 19 ++++--------------- docs/versionhistory.rst | 2 ++ tests/test_triggers.py | 20 -------------------- 3 files changed, 6 insertions(+), 35 deletions(-) diff --git a/apscheduler/triggers/base.py b/apscheduler/triggers/base.py index ce2526a88..55d010dba 100644 --- a/apscheduler/triggers/base.py +++ b/apscheduler/triggers/base.py @@ -22,27 +22,16 @@ def get_next_fire_time(self, previous_fire_time, now): def _apply_jitter(self, next_fire_time, jitter, now): """ - Randomize ``next_fire_time`` by adding or subtracting a random value (the jitter). If the - resulting datetime is in the past, returns the initial ``next_fire_time`` without jitter. - - ``next_fire_time - jitter <= result <= next_fire_time + jitter`` + Randomize ``next_fire_time`` by adding a random value (the jitter). :param datetime.datetime|None next_fire_time: next fire time without jitter applied. If ``None``, returns ``None``. - :param int|None jitter: maximum number of seconds to add or subtract to - ``next_fire_time``. If ``None`` or ``0``, returns ``next_fire_time`` + :param int|None jitter: maximum number of seconds to add to ``next_fire_time`` + (if ``None`` or ``0``, returns ``next_fire_time``) :param datetime.datetime now: current datetime :return datetime.datetime|None: next fire time with a jitter. """ if next_fire_time is None or not jitter: return next_fire_time - next_fire_time_with_jitter = next_fire_time + timedelta( - seconds=random.uniform(-jitter, jitter)) - - if next_fire_time_with_jitter < now: - # Next fire time with jitter is in the past. - # Ignore jitter to avoid false misfire. - return next_fire_time - - return next_fire_time_with_jitter + return next_fire_time + timedelta(seconds=random.uniform(0, jitter)) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 623b3e273..7df28887d 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -12,6 +12,8 @@ APScheduler, see the :doc:`migration section `. * Pinned ``tzlocal`` to a version compatible with pytz * Fixed deprecation warnings on the MongoDB job store and increased the minimum PyMongo version to 3.0 +* Ensured that jitter is always non-negative to prevent triggers from firing more often than + intended 3.6.3 diff --git a/tests/test_triggers.py b/tests/test_triggers.py index a63748586..ea0d1c16f 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -50,16 +50,6 @@ def test_jitter_positive(self, monkeypatch): trigger = _DummyTriggerWithJitter(dt, 60) assert trigger.get_next_fire_time(None, now) == expected_dt - def test_jitter_in_past_but_initial_date_in_future(self, monkeypatch): - monkeypatch.setattr(random, 'uniform', lambda a, b: -30.) - - now = datetime(2017, 5, 25, 13, 40, 44) - dt = datetime(2017, 5, 25, 13, 40, 47) - expected_dt = dt - - trigger = _DummyTriggerWithJitter(dt, 60) - assert trigger.get_next_fire_time(None, now) == expected_dt - def test_jitter_in_future_but_initial_date_in_past(self, monkeypatch): monkeypatch.setattr(random, 'uniform', lambda a, b: 30.) @@ -70,16 +60,6 @@ def test_jitter_in_future_but_initial_date_in_past(self, monkeypatch): trigger = _DummyTriggerWithJitter(dt, 60) assert trigger.get_next_fire_time(None, now) == expected_dt - def test_jitter_misfire(self, monkeypatch): - monkeypatch.setattr(random, 'uniform', lambda a, b: -30.) - - now = datetime(2017, 5, 25, 13, 40, 44) - dt = datetime(2017, 5, 25, 13, 40, 40) - expected_dt = dt - - trigger = _DummyTriggerWithJitter(dt, 60) - assert trigger.get_next_fire_time(None, now) == expected_dt - def test_jitter_is_now(self, monkeypatch): monkeypatch.setattr(random, 'uniform', lambda a, b: 4.)