diff --git a/newsfragments/3095.bugfix.rst b/newsfragments/3095.bugfix.rst new file mode 100644 index 000000000..7a7d756e5 --- /dev/null +++ b/newsfragments/3095.bugfix.rst @@ -0,0 +1 @@ +Update :func:`trio.sleep_forever` to be `NoReturn`. diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py index 515f536c1..574e1db1a 100644 --- a/src/trio/_tests/test_timeouts.py +++ b/src/trio/_tests/test_timeouts.py @@ -86,6 +86,20 @@ async def sleep_3() -> None: await check_takes_about(sleep_3, TARGET) +async def test_cannot_wake_sleep_forever() -> None: + # Test an error occurs if you manually wake sleep_forever(). + task = trio.lowlevel.current_task() + + async def wake_task() -> None: + await trio.lowlevel.checkpoint() + trio.lowlevel.reschedule(task, outcome.Value(None)) + + async with trio.open_nursery() as nursery: + nursery.start_soon(wake_task) + with pytest.raises(RuntimeError): + await trio.sleep_forever() + + class TimeoutScope(Protocol): def __call__(self, seconds: float, *, shield: bool) -> trio.CancelScope: ... diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py index 1a5e2e1db..417f1818f 100644 --- a/src/trio/_timeouts.py +++ b/src/trio/_timeouts.py @@ -3,7 +3,7 @@ import math import sys from contextlib import contextmanager -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, NoReturn import trio @@ -58,13 +58,14 @@ def move_on_after( ) -async def sleep_forever() -> None: +async def sleep_forever() -> NoReturn: """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) + raise RuntimeError("Should never have been rescheduled!") async def sleep_until(deadline: float) -> None: