diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 2339843a4..3a3f0605d 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -26,6 +26,7 @@ from __future__ import annotations import asyncio import datetime +import logging from typing import ( Any, Awaitable, @@ -48,6 +49,8 @@ from collections.abc import Sequence from discord.backoff import ExponentialBackoff from discord.utils import MISSING +_log = logging.getLogger(__name__) + # fmt: off __all__ = ( 'loop', @@ -173,6 +176,25 @@ class Loop(Generic[LF]): if not self._last_iteration_failed: self._last_iteration = self._next_iteration self._next_iteration = self._get_next_sleep_time() + + # In order to account for clock drift, we need to ensure that + # the next iteration always follows the last iteration. + # Sometimes asyncio is cheeky and wakes up a few microseconds before our target + # time, causing it to repeat a run. + while self._next_iteration <= self._last_iteration: + _log.warn( + ( + 'Clock drift detected for task %s. Woke up at %s but needed to sleep until %s. ' + 'Sleeping until %s again to correct clock' + ), + self.coro.__qualname__, + discord.utils.utcnow(), + self._next_iteration, + self._next_iteration, + ) + await self._try_sleep_until(self._next_iteration) + self._next_iteration = self._get_next_sleep_time() + try: await self.coro(*args, **kwargs) self._last_iteration_failed = False