Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/2493.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Timeout functions now raise `ValueError` if passed `math.nan`. This includes `trio.sleep`, `trio.sleep_until`, `trio.move_on_at`, `trio.move_on_after`, `trio.fail_at` and `trio.fail_after`.
36 changes: 26 additions & 10 deletions trio/_tests/test_timeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ async def sleep_2():

await check_takes_about(sleep_2, TARGET)

with pytest.raises(ValueError):
await sleep(-1)

with assert_checkpoints():
await sleep(0)
# This also serves as a test of the trivial move_on_at
Expand All @@ -66,10 +63,6 @@ async def sleep_2():

@slow
async def test_move_on_after():
with pytest.raises(ValueError):
with move_on_after(-1):
pass # pragma: no cover

async def sleep_3():
with move_on_after(TARGET):
await sleep(100)
Expand Down Expand Up @@ -99,6 +92,29 @@ async def sleep_5():
with fail_after(100):
await sleep(0)

with pytest.raises(ValueError):
with fail_after(-1):
pass # pragma: no cover

async def test_timeouts_raise_value_error():
# deadlines are allowed to be negative, but not delays.
# neither delays nor deadlines are allowed to be NaN

nan = float("nan")

for fun, val in (
(sleep, -1),
(sleep, nan),
(sleep_until, nan),
):
with pytest.raises(ValueError):
await fun(val)

for cm, val in (
(fail_after, -1),
(fail_after, nan),
(fail_at, nan),
(move_on_after, -1),
(move_on_after, nan),
(move_on_at, nan),
):
with pytest.raises(ValueError):
with cm(val):
pass # pragma: no cover
23 changes: 19 additions & 4 deletions trio/_timeouts.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import math
from contextlib import contextmanager

import trio
Expand All @@ -10,7 +11,12 @@ def move_on_at(deadline):
Args:
deadline (float): The deadline.

Raises:
ValueError: if deadline is NaN.

"""
if math.isnan(deadline):
raise ValueError("deadline must not be NaN")
Comment thread
jakkdl marked this conversation as resolved.
return trio.CancelScope(deadline=deadline)


Expand All @@ -22,10 +28,9 @@ def move_on_after(seconds):
seconds (float): The timeout.

Raises:
ValueError: if timeout is less than zero.
ValueError: if timeout is less than zero or NaN.

"""

if seconds < 0:
raise ValueError("timeout must be non-negative")
return move_on_at(trio.current_time() + seconds)
Expand All @@ -52,6 +57,9 @@ async def sleep_until(deadline):
the past, in which case this function executes a checkpoint but
does not block.

Raises:
ValueError: if deadline is NaN.

"""
with move_on_at(deadline):
await sleep_forever()
Expand All @@ -65,7 +73,7 @@ async def sleep(seconds):
insert a checkpoint without actually blocking.

Raises:
ValueError: if *seconds* is negative.
ValueError: if *seconds* is negative or NaN.

"""
if seconds < 0:
Expand Down Expand Up @@ -96,9 +104,13 @@ def fail_at(deadline):
:func:`fail_at`, then it's caught and :exc:`TooSlowError` is raised in its
place.

Args:
deadline (float): The deadline.

Raises:
TooSlowError: if a :exc:`Cancelled` exception is raised in this scope
and caught by the context manager.
ValueError: if deadline is NaN.

"""

Expand All @@ -119,10 +131,13 @@ def fail_after(seconds):
it's caught and discarded. When it reaches :func:`fail_after`, then it's
caught and :exc:`TooSlowError` is raised in its place.

Args:
seconds (float): The timeout.

Comment on lines +134 to +136
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment that I really don't like how this is so easy to forget :S

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like something that can be auto-generated / have the signature displayed in another manner in the doc, once the public interface is fully typed.

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.
ValueError: if *seconds* is less than zero or NaN.

"""
if seconds < 0:
Expand Down