8000 gh-95051: ensure that timeouts scheduled with `asyncio.Timeout` that … · python/cpython@19d9536 · GitHub
[go: up one dir, main page]

Skip to content

Commit 19d9536

Browse files
gh-95051: ensure that timeouts scheduled with asyncio.Timeout that have already expired are deliverered promptly (GH-95109) (GH-95216)
Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> (cherry picked from commit 0c6f898) Co-authored-by: Thomas Grainger <tagrain@gmail.com>
1 parent 2fb64a0 commit 19d9536

File tree

4 files changed

+32
-4
lines changed

4 files changed

+32
-4
lines changed

Doc/library/asyncio-task.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,9 @@ Timeouts
625625

626626
If *when* is a float, it is set as the new deadline.
627627

628+
if *when* is in the past, the timeout will trigger on the next
629+
iteration of the event loop.
630+
628631
.. method:: expired() -> bool
629632

630633
Return whether the context manager has exceeded its deadline

Lib/asyncio/timeouts.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ def reschedule(self, when: Optional[float]) -> None:
5252
self._timeout_handler = None
5353
else:
5454
loop = events.get_running_loop()
55-
self._timeout_handler = loop.call_at(
56-
when,
57-
self._on_timeout,
58-
)
55+
if when <= loop.time():
56+
self._timeout_handler = loop.call_soon(self._on_timeout)
57+
else:
58+
self._timeout_handler = loop.call_at(when, self._on_timeout)
5959

6060
def expired(self) -> bool:
6161
"""Is timeout expired during execution?"""

Lib/test/test_asyncio/test_timeouts.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,30 @@ async def test_timeout_zero(self):
106106
self.assertLess(t1-t0, 2)
107107
self.assertTrue(t0 <= cm.when() <= t1)
108108

109+
async def test_timeout_zero_sleep_zero(self):
110+
loop = asyncio.get_running_loop()
111+
t0 = loop.time()
112+
with self.assertRaises(TimeoutError):
113+
async with asyncio.timeout(0) as cm:
114+
await asyncio.sleep(0)
115+
t1 = loop.time()
116+
self.assertTrue(cm.expired())
117+
# 2 sec for slow CI boxes
118+
self.assertLess(t1-t0, 2)
119+
self.assertTrue(t0 <= cm.when() <= t1)
120+
121+
async def test_timeout_in_the_past_sleep_zero(self):
122+
loop = asyncio.get_running_loop()
123+
t0 = loop.time()
124+
with self.assertRaises(TimeoutError):
125+
async with asyncio.timeout(-11) as cm:
126+
await asyncio.sleep(0)
127+
t1 = loop.time()
128+
self.assertTrue(cm.expired())
129+
# 2 sec for slow CI boxes
130+
self.assertLess(t1-t0, 2)
131+
self.assertTrue(t0 >= cm.when() <= t1)
132+
109133
async def test_foreign_exception_passed(self):
110134
with self.assertRaises(KeyError):
111135
async with asyncio.timeout(0.01) as cm:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure that timeouts scheduled with :class:`asyncio.Timeout` that have already expired are delivered promptly.

0 commit comments

Comments
 (0)
0