You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
3.8 KiB
Python

from contextlib import contextmanager
import trio
def move_on_at(deadline):
"""Use as a context manager to create a cancel scope with the given
absolute deadline.
Args:
deadline (float): The deadline.
"""
return trio.CancelScope(deadline=deadline)
def move_on_after(seconds):
"""Use as a context manager to create a cancel scope whose deadline is
set to now + *seconds*.
Args:
seconds (float): The timeout.
Raises:
ValueError: if timeout is less than zero.
"""
if seconds < 0:
raise ValueError("timeout must be non-negative")
return move_on_at(trio.current_time() + seconds)
async def sleep_forever():
"""Pause execution of the current task forever (or until cancelled).
Equivalent to calling ``await sleep(math.inf)``.
"""
await trio.lowlevel.wait_task_rescheduled(lambda _: trio.lowlevel.Abort.SUCCEEDED)
async def sleep_until(deadline):
"""Pause execution of the current task until the given time.
The difference between :func:`sleep` and :func:`sleep_until` is that the
former takes a relative time and the latter takes an absolute time
according to Trio's internal clock (as returned by :func:`current_time`).
Args:
deadline (float): The time at which we should wake up again. May be in
the past, in which case this function executes a checkpoint but
does not block.
"""
with move_on_at(deadline):
await sleep_forever()
async def sleep(seconds):
"""Pause execution of the current task for the given number of seconds.
Args:
seconds (float): The number of seconds to sleep. May be zero to
insert a checkpoint without actually blocking.
Raises:
ValueError: if *seconds* is negative.
"""
if seconds < 0:
raise ValueError("duration must be non-negative")
if seconds == 0:
await trio.lowlevel.checkpoint()
else:
await sleep_until(trio.current_time() + seconds)
class TooSlowError(Exception):
"""Raised by :func:`fail_after` and :func:`fail_at` if the timeout
expires.
"""
@contextmanager
def fail_at(deadline):
"""Creates a cancel scope with the given deadline, and raises an error if it
is actually cancelled.
This function and :func:`move_on_at` are similar in that both create a
cancel scope with a given absolute deadline, and if the deadline expires
then both will cause :exc:`Cancelled` to be raised within the scope. The
difference is that when the :exc:`Cancelled` exception reaches
:func:`move_on_at`, it's caught and discarded. When it reaches
:func:`fail_at`, then it's caught and :exc:`TooSlowError` is raised in its
place.
Raises:
TooSlowError: if a :exc:`Cancelled` exception is raised in this scope
and caught by the context manager.
"""
with move_on_at(deadline) as scope:
yield scope
if scope.cancelled_caught:
raise TooSlowError
def fail_after(seconds):
"""Creates a cancel scope with the given timeout, and raises an error if
it is actually cancelled.
This function and :func:`move_on_after` are similar in that both create a
cancel scope with a given timeout, and if the timeout expires then both
will cause :exc:`Cancelled` to be raised within the scope. The difference
is that when the :exc:`Cancelled` exception reaches :func:`move_on_after`,
it's caught and discarded. When it reaches :func:`fail_after`, then it's
caught and :exc:`TooSlowError` is raised in its place.
Raises:
TooSlowError: if a :exc:`Cancelled` exception is raised in this scope
and caught by the context manager.
ValueError: if *seconds* is less than zero.
"""
if seconds < 0:
raise ValueError("timeout must be non-negative")
return fail_at(trio.current_time() + seconds)