]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-95051: ensure that timeouts scheduled with `asyncio.Timeout` that have already...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 26 Jul 2022 10:16:12 +0000 (03:16 -0700)
committerGitHub <noreply@github.com>
Tue, 26 Jul 2022 10:16:12 +0000 (12:16 +0200)
Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
(cherry picked from commit 0c6f898005099be189ee65bcfda659f5fc13b802)

Co-authored-by: Thomas Grainger <tagrain@gmail.com>
Doc/library/asyncio-task.rst
Lib/asyncio/timeouts.py
Lib/test/test_asyncio/test_timeouts.py
Misc/NEWS.d/next/Library/2022-07-21-22-59-22.gh-issue-95109.usxA9r.rst [new file with mode: 0644]

index a307d22e456ebf001e6d72beb833555306b21dc6..a6b638c1124094e238d132c6f8decfe1ba1e0742 100644 (file)
@@ -625,6 +625,9 @@ Timeouts
 
             If *when* is a float, it is set as the new deadline.
 
+            if *when* is in the past, the timeout will trigger on the next
+            iteration of the event loop.
+
         .. method:: expired() -> bool
 
            Return whether the context manager has exceeded its deadline
index a89205348ff24ca7043997e83c5870323ed4a86a..94d25535fbc0595760877415166bfc8edd6ba1a8 100644 (file)
@@ -52,10 +52,10 @@ class Timeout:
             self._timeout_handler = None
         else:
             loop = events.get_running_loop()
-            self._timeout_handler = loop.call_at(
-                when,
-                self._on_timeout,
-            )
+            if when <= loop.time():
+                self._timeout_handler = loop.call_soon(self._on_timeout)
+            else:
+                self._timeout_handler = loop.call_at(when, self._on_timeout)
 
     def expired(self) -> bool:
         """Is timeout expired during execution?"""
index ef1ab0acb390d20ce2c5ea2e8ec1af560526dd20..3a54297050f14b146c32dee0768e7221b6fa4937 100644 (file)
@@ -106,6 +106,30 @@ class TimeoutTests(unittest.IsolatedAsyncioTestCase):
         self.assertLess(t1-t0, 2)
         self.assertTrue(t0 <= cm.when() <= t1)
 
+    async def test_timeout_zero_sleep_zero(self):
+        loop = asyncio.get_running_loop()
+        t0 = loop.time()
+        with self.assertRaises(TimeoutError):
+            async with asyncio.timeout(0) as cm:
+                await asyncio.sleep(0)
+        t1 = loop.time()
+        self.assertTrue(cm.expired())
+        # 2 sec for slow CI boxes
+        self.assertLess(t1-t0, 2)
+        self.assertTrue(t0 <= cm.when() <= t1)
+
+    async def test_timeout_in_the_past_sleep_zero(self):
+        loop = asyncio.get_running_loop()
+        t0 = loop.time()
+        with self.assertRaises(TimeoutError):
+            async with asyncio.timeout(-11) as cm:
+                await asyncio.sleep(0)
+        t1 = loop.time()
+        self.assertTrue(cm.expired())
+        # 2 sec for slow CI boxes
+        self.assertLess(t1-t0, 2)
+        self.assertTrue(t0 >= cm.when() <= t1)
+
     async def test_foreign_exception_passed(self):
         with self.assertRaises(KeyError):
             async with asyncio.timeout(0.01) as cm:
diff --git a/Misc/NEWS.d/next/Library/2022-07-21-22-59-22.gh-issue-95109.usxA9r.rst b/Misc/NEWS.d/next/Library/2022-07-21-22-59-22.gh-issue-95109.usxA9r.rst
new file mode 100644 (file)
index 0000000..40196dd
--- /dev/null
@@ -0,0 +1 @@
+Ensure that timeouts scheduled with :class:`asyncio.Timeout` that have already expired are delivered promptly.