Skip to content

Commit

Permalink
Deadline to use a monotonic timer (#8597)
Browse files Browse the repository at this point in the history
  • Loading branch information
crusaderky committed Mar 22, 2024
1 parent a2db1d9 commit abd1539
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 28 deletions.
19 changes: 8 additions & 11 deletions distributed/tests/test_deadline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,29 @@


def test_deadline():
before_start = time()
deadline = Deadline.after(5)
after_start = time()

assert deadline.duration == 5
assert deadline.expired is False
assert deadline.expires is True
assert deadline.expires_at - deadline.started_at == 5
assert deadline.expires_at_mono - deadline.started_at_mono == 5
assert 4 < deadline.expires_at - deadline.started_at < 6
assert 0 <= deadline.elapsed <= 1
assert 4 <= deadline.remaining <= 5

deadline2 = Deadline(deadline.expires_at)

assert deadline.expires_at == deadline2.expires_at


def test_infinite_deadline():
deadline = Deadline(None)

assert deadline.expires_at_mono is None
assert deadline.expires_at is None
assert deadline.expired is False
assert deadline.expires is False
assert deadline.expires_at is None
assert deadline.remaining is None
assert deadline.duration is None
assert 0 <= deadline.elapsed <= 1

deadline2 = Deadline.after(None)

assert deadline2.expires_at is None
assert deadline2.expires_at_mono is None


@gen_test()
Expand Down
55 changes: 38 additions & 17 deletions distributed/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1841,52 +1841,73 @@ def is_python_shutting_down() -> bool:
class Deadline:
"""Utility class tracking a deadline and the progress toward it"""

#: Expiry time of the deadline in seconds since the epoch
#: Expiry time of the deadline in seconds since the start of the monotonic timer
#: or None if the deadline never expires
expires_at: float | None
expires_at_mono: float | None
#: Seconds since the epoch when the deadline was created
started_at_mono: float
#: Seconds since the start of the monotonic timer when the deadline was created
started_at: float

__slots__ = tuple(__annotations__)

def __init__(self, expires_at: float | None = None):
self.expires_at = expires_at
self.started_at = time()
self.started_at_mono = monotonic()
if expires_at is not None:
self.expires_at_mono = expires_at - self.started_at + self.started_at_mono
else:
self.expires_at_mono = None

@classmethod
def after(cls, duration: float | None = None) -> Deadline:
"""Create a new ``Deadline`` that expires in ``duration`` seconds
or never if ``duration`` is None"""
started_at = time()
expires_at = duration + started_at if duration is not None else duration
deadline = cls(expires_at)
deadline.started_at = started_at
return deadline
or never if ``duration`` is None
"""
inst = cls()
if duration is not None:
inst.expires_at_mono = inst.started_at_mono + duration
return inst

@property
def expires_at(self) -> float | None:
"""Expiry time of the deadline in seconds since the unix epoch
or None if the deadline never expires.
Note that this can change over time if the wall clock is adjusted by the OS.
"""
if (exp := self.expires_at_mono) is None:
return None
return exp - monotonic() + time()

@property
def duration(self) -> float | None:
"""Seconds between the creation and expiration time of the deadline
if the deadline expires, None otherwise"""
if self.expires_at is None:
if the deadline expires, None otherwise
"""
if (exp := self.expires_at_mono) is None:
return None
return self.expires_at - self.started_at
return exp - self.started_at_mono

@property
def expires(self) -> bool:
"""Whether the deadline ever expires"""
return self.expires_at is not None
return self.expires_at_mono is not None

@property
def elapsed(self) -> float:
"""Seconds that elapsed since the deadline was created"""
return time() - self.started_at
return monotonic() - self.started_at_mono

@property
def remaining(self) -> float | None:
"""Seconds remaining until the deadline expires if an expiry time is set,
None otherwise"""
if self.expires_at is None:
None otherwise
"""
if (exp := self.expires_at_mono) is None:
return None
else:
return max(0, self.expires_at - time())
return max(0, exp - monotonic())

@property
def expired(self) -> bool:
Expand Down

0 comments on commit abd1539

Please sign in to comment.