From 3a684251325a693a0584952abd97954c236550a6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:09:12 -0400 Subject: [PATCH 01/17] Make concurrent.futures.TimeoutError and asyncio.TimeoutError unique --- Doc/library/asyncio-exceptions.rst | 36 +++++++++++++++++++++++++++++- Doc/library/concurrent.futures.rst | 6 ++++- Lib/asyncio/exceptions.py | 10 ++++++++- Lib/concurrent/futures/_base.py | 4 +++- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/Doc/library/asyncio-exceptions.rst b/Doc/library/asyncio-exceptions.rst index 7ad9103ca3fdfc..01f75e21dd9004 100644 --- a/Doc/library/asyncio-exceptions.rst +++ b/Doc/library/asyncio-exceptions.rst @@ -13,13 +13,47 @@ Exceptions .. exception:: TimeoutError - A deprecated alias of :exc:`TimeoutError`, + A near-alias of :exc:`TimeoutError`, raised when the operation has exceeded the given deadline. .. versionchanged:: 3.11 This class was made an alias of :exc:`TimeoutError`. + .. versionchanged:: 3.14 + + This class was made unique, but subclasses :exc:`TimeoutError`. + + +.. exception:: CancelledError + + The operation has been cancelled. + + This exception can be caught to perform custom operations + when asyncio Tasks are cancelled. In almost all situations the + exception must be re-raised. + + .. versionchanged:: 3.8 + + :exc:`CancelledError` is now a subclass of :class:`BaseException` rather than :class:`Exception`. + + +.. exception:: InvalidStateError + + Invalid internal state of :class:`Task` or :class:`Future`. + + Can be raised in situations like setting a result value for a + *Future* object that already has a result value set. + + +.. exception:: SendfileNotAvailableError + + The "sendfile" syscall is not available for the given + socket or file type. + + A subclass of :exc:`RuntimeError`. + + .. exception:: CancelledError diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index e3b24451188cc4..044499efe2824b 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -542,13 +542,17 @@ Exception classes .. exception:: TimeoutError - A deprecated alias of :exc:`TimeoutError`, + A near-alias of :exc:`TimeoutError`, raised when a future operation exceeds the given timeout. .. versionchanged:: 3.11 This class was made an alias of :exc:`TimeoutError`. + .. versionchanged:: 3.14 + + This class was made unique, but subclasses :exc:`TimeoutError`. + .. exception:: BrokenExecutor diff --git a/Lib/asyncio/exceptions.py b/Lib/asyncio/exceptions.py index 5ece595aad6475..b13ffc1de8a8ee 100644 --- a/Lib/asyncio/exceptions.py +++ b/Lib/asyncio/exceptions.py @@ -11,7 +11,15 @@ class CancelledError(BaseException): """The Future or Task was cancelled.""" -TimeoutError = TimeoutError # make local alias for the standard exception +# GH-124308, BPO-32413: Originally, asyncio.TimeoutError was it's +# own unique exception. This was a bit of a gotcha because catching TimeoutError +# didn't catch asyncio.TimeoutError. So, it was turned into an alias +# of TimeoutError directly. Unfortunately, this made it effectively +# impossible to differentiate between asyncio.TimeoutError or a TimeoutError +# raised by a third party, so this is now a unique exception again (that inherits +# from TimeoutError to address the first problem). +class TimeoutError(TimeoutError): + """Operation timed out.""" class InvalidStateError(Exception): diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index 707fcdfde79acd..4b4ccdcf1301e7 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -42,7 +42,9 @@ class CancelledError(Error): """The Future was cancelled.""" pass -TimeoutError = TimeoutError # make local alias for the standard exception +# Intentional, see GH-124308 +class TimeoutError(TimeoutError): + pass class InvalidStateError(Error): """The operation is not allowed in this state.""" From eeaf8bf58810cddc29d0088c4ccaf70a423aa9da Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:18:17 -0400 Subject: [PATCH 02/17] Add test for asyncio. --- Lib/test/test_asyncio/test_timeouts.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/test/test_asyncio/test_timeouts.py b/Lib/test/test_asyncio/test_timeouts.py index f5543e191d07ff..272fb0c69f31c4 100644 --- a/Lib/test/test_asyncio/test_timeouts.py +++ b/Lib/test/test_asyncio/test_timeouts.py @@ -406,6 +406,15 @@ async def task(): self.assertIsNone(e3.__cause__) self.assertIs(e2.__context__, e3) + async def test_timeouterror_is_unique(self): + # See GH-124308 + with self.assertRaises(asyncio.TimeoutError) as err: + async with asyncio.timeout(0.01): + pass + + # See BPO-42413 + self.assertIsTrue(issubclass(err, TimeoutError)) + if __name__ == '__main__': unittest.main() From 6c1d8e5d419df94369006a4aaad28b0a98288b5a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:26:45 -0400 Subject: [PATCH 03/17] Add tests for concurrent.futures --- Lib/test/test_concurrent_futures/test_future.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_concurrent_futures/test_future.py b/Lib/test/test_concurrent_futures/test_future.py index 4066ea1ee4b367..fd5fc67784f9d1 100644 --- a/Lib/test/test_concurrent_futures/test_future.py +++ b/Lib/test/test_concurrent_futures/test_future.py @@ -4,6 +4,7 @@ from concurrent import futures from concurrent.futures._base import ( PENDING, RUNNING, CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED, Future) +from contextlib import suppress from test import support @@ -196,6 +197,16 @@ def test_result_with_timeout(self): self.assertRaises(OSError, EXCEPTION_FUTURE.result, timeout=0) self.assertEqual(SUCCESSFUL_FUTURE.result(timeout=0), 42) + # BPO-42413: Catching TimeoutError should catch futures.TimeoutError + with self.assertRaises(TimeoutError) as err: + self.assertRaises(futures.TimeoutError, + PENDING_FUTURE.result, timeout=0) + + with self.assertRaises(TimeoutError): + # GH-124308: Catching futures.TimeoutError should not allow TimeoutError + with suppress(futures.TimeoutError): + raise TimeoutError + def test_result_with_success(self): # TODO(brian@sweetapp.com): This test is timing dependent. def notification(): From a1d6f8d620a7059c80c131363a6aed88c26ef160 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:30:11 -0400 Subject: [PATCH 04/17] Update asyncio test. --- Lib/test/test_asyncio/test_timeouts.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_asyncio/test_timeouts.py b/Lib/test/test_asyncio/test_timeouts.py index 272fb0c69f31c4..da62ba4dc75183 100644 --- a/Lib/test/test_asyncio/test_timeouts.py +++ b/Lib/test/test_asyncio/test_timeouts.py @@ -5,6 +5,7 @@ import asyncio +from contextlib import suppress from test.test_asyncio.utils import await_without_task @@ -407,13 +408,15 @@ async def task(): self.assertIs(e2.__context__, e3) async def test_timeouterror_is_unique(self): - # See GH-124308 - with self.assertRaises(asyncio.TimeoutError) as err: + # BPO-42413: Catching TimeoutError should include asyncio.TimeoutError + with self.assertRaises(TimeoutError): async with asyncio.timeout(0.01): - pass + await asyncio.sleep(1) - # See BPO-42413 - self.assertIsTrue(issubclass(err, TimeoutError)) + with self.assertRaises(TimeoutError): + # GH-124308: Catching asyncio.TimeoutError should not include TimeoutError + with suppress(asyncio.TimeoutError): + raise TimeoutError if __name__ == '__main__': From 969f9937b1f3160e63adfcc8f1f698793b0eee63 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:31:59 -0400 Subject: [PATCH 05/17] Allow test_future to be executed manually. --- Lib/test/test_concurrent_futures/test_future.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_concurrent_futures/test_future.py b/Lib/test/test_concurrent_futures/test_future.py index fd5fc67784f9d1..c00af2705e2742 100644 --- a/Lib/test/test_concurrent_futures/test_future.py +++ b/Lib/test/test_concurrent_futures/test_future.py @@ -8,7 +8,7 @@ from test import support -from .util import ( +from test.test_concurrent_futures.util import ( PENDING_FUTURE, RUNNING_FUTURE, CANCELLED_FUTURE, CANCELLED_AND_NOTIFIED_FUTURE, EXCEPTION_FUTURE, SUCCESSFUL_FUTURE, BaseTestCase, create_future, setup_module) From 9eb559ade7fafe4fbb69cf1160d806dfcd423633 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:37:06 -0400 Subject: [PATCH 06/17] Fix test for concurrent.futures --- Lib/test/test_concurrent_futures/test_future.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_concurrent_futures/test_future.py b/Lib/test/test_concurrent_futures/test_future.py index c00af2705e2742..179cc2d8ac235a 100644 --- a/Lib/test/test_concurrent_futures/test_future.py +++ b/Lib/test/test_concurrent_futures/test_future.py @@ -198,12 +198,11 @@ def test_result_with_timeout(self): self.assertEqual(SUCCESSFUL_FUTURE.result(timeout=0), 42) # BPO-42413: Catching TimeoutError should catch futures.TimeoutError - with self.assertRaises(TimeoutError) as err: - self.assertRaises(futures.TimeoutError, - PENDING_FUTURE.result, timeout=0) + with self.assertRaises(TimeoutError): + raise futures.TimeoutError with self.assertRaises(TimeoutError): - # GH-124308: Catching futures.TimeoutError should not allow TimeoutError + # GH-124308: Catching futures.TimeoutError should not catch TimeoutError with suppress(futures.TimeoutError): raise TimeoutError From e9ed9d07761d26b1986c0f0ef09d198c587f5a46 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:45:16 -0400 Subject: [PATCH 07/17] Add support for catching TimeoutError for multiprocessing.TimeoutError --- Lib/multiprocessing/context.py | 2 +- Lib/test/_test_multiprocessing.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/multiprocessing/context.py b/Lib/multiprocessing/context.py index ddcc7e7900999e..31d8727ee859b7 100644 --- a/Lib/multiprocessing/context.py +++ b/Lib/multiprocessing/context.py @@ -17,7 +17,7 @@ class ProcessError(Exception): class BufferTooShort(ProcessError): pass -class TimeoutError(ProcessError): +class TimeoutError(ProcessError, TimeoutError): pass class AuthenticationError(ProcessError): diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 4b3a0645cfc84a..1d7e43b1cb64fb 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2605,6 +2605,15 @@ def test_async_timeout(self): get = TimingWrapper(res.get) self.assertRaises(multiprocessing.TimeoutError, get, timeout=TIMEOUT2) self.assertTimingAlmostEqual(get.elapsed, TIMEOUT2) + + # BPO-42413: Catching TimeoutError should catch multiprocessing.TimeoutError + with self.assertRaises(TimeoutError): + raise multiprocessing.TimeoutError + + # GH-124308: Catching multiprocessing.TimeoutError should not catch TimeoutError + with self.assertRaises(TimeoutError): + with suppress(multiprocessing.TimeoutError): + raise TimeoutError finally: if event is not None: event.set() From 4e81a64d07a0ad6ef42bbde22d9efd37b4035085 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:47:34 -0400 Subject: [PATCH 08/17] Update documentation for multiprocessing. --- Doc/library/multiprocessing.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index 9fa76c4ce59d00..6ac2ef70ffed53 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -726,6 +726,10 @@ The :mod:`multiprocessing` package mostly replicates the API of the Raised by methods with a timeout when the timeout expires. + .. versionchanged:: 3.14 + + This class now subclasses :exc:`TimeoutError` + Pipes and Queues ^^^^^^^^^^^^^^^^ From fa52d3d0d3830c099a2996c2df062a1de8041586 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:51:04 -0400 Subject: [PATCH 09/17] Fix documentation diff. --- Doc/library/asyncio-exceptions.rst | 34 ++---------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/Doc/library/asyncio-exceptions.rst b/Doc/library/asyncio-exceptions.rst index 01f75e21dd9004..9c9d6df5ab0d49 100644 --- a/Doc/library/asyncio-exceptions.rst +++ b/Doc/library/asyncio-exceptions.rst @@ -13,7 +13,7 @@ Exceptions .. exception:: TimeoutError - A near-alias of :exc:`TimeoutError`, + A deprecated alias of :exc:`TimeoutError`, raised when the operation has exceeded the given deadline. .. versionchanged:: 3.11 @@ -22,37 +22,7 @@ Exceptions .. versionchanged:: 3.14 - This class was made unique, but subclasses :exc:`TimeoutError`. - - -.. exception:: CancelledError - - The operation has been cancelled. - - This exception can be caught to perform custom operations - when asyncio Tasks are cancelled. In almost all situations the - exception must be re-raised. - - .. versionchanged:: 3.8 - - :exc:`CancelledError` is now a subclass of :class:`BaseException` rather than :class:`Exception`. - - -.. exception:: InvalidStateError - - Invalid internal state of :class:`Task` or :class:`Future`. - - Can be raised in situations like setting a result value for a - *Future* object that already has a result value set. - - -.. exception:: SendfileNotAvailableError - - The "sendfile" syscall is not available for the given - socket or file type. - - A subclass of :exc:`RuntimeError`. - + This class was made a unique subclass of :exc:`TimeoutError`. .. exception:: CancelledError From 509fddc6176948c697a13decdcf07b2614d47549 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:51:58 -0400 Subject: [PATCH 10/17] Unify documentation notes. --- Doc/library/concurrent.futures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index 044499efe2824b..fd6404aa913b55 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -551,7 +551,7 @@ Exception classes .. versionchanged:: 3.14 - This class was made unique, but subclasses :exc:`TimeoutError`. + This class was made a unique subclass of :exc:`TimeoutError`. .. exception:: BrokenExecutor From df73e9e6420bbbfb22b3e76627e84e7309d314c1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:55:59 -0400 Subject: [PATCH 11/17] Update source code to use unique TimeoutError --- Lib/asyncio/tasks.py | 2 +- Lib/asyncio/timeouts.py | 4 ++-- Lib/concurrent/futures/_base.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 2112dd4b99d17f..cf3dfb5a606dce 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -486,7 +486,7 @@ async def wait_for(fut, timeout): try: return fut.result() except exceptions.CancelledError as exc: - raise TimeoutError from exc + raise exceptions.TimeoutError from exc async with timeouts.timeout(timeout): return await fut diff --git a/Lib/asyncio/timeouts.py b/Lib/asyncio/timeouts.py index e6f5100691d362..863854449e8fbc 100644 --- a/Lib/asyncio/timeouts.py +++ b/Lib/asyncio/timeouts.py @@ -113,7 +113,7 @@ async def __aexit__( # Since there are no new cancel requests, we're # handling this. if issubclass(exc_type, exceptions.CancelledError): - raise TimeoutError from exc_val + raise exceptions.TimeoutError from exc_val elif exc_val is not None: self._insert_timeout_error(exc_val) if isinstance(exc_val, ExceptionGroup): @@ -135,7 +135,7 @@ def _on_timeout(self) -> None: def _insert_timeout_error(exc_val: BaseException) -> None: while exc_val.__context__ is not None: if isinstance(exc_val.__context__, exceptions.CancelledError): - te = TimeoutError() + te = exceptions.TimeoutError() te.__context__ = te.__cause__ = exc_val.__context__ exc_val.__context__ = te break diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index 4b4ccdcf1301e7..4faed162142dcd 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -42,7 +42,7 @@ class CancelledError(Error): """The Future was cancelled.""" pass -# Intentional, see GH-124308 +# See GH-124308 class TimeoutError(TimeoutError): pass From f903bbe55410ff346610dc68e0839f567e7262a0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 15:58:36 -0400 Subject: [PATCH 12/17] Shorten long comment. --- Lib/asyncio/exceptions.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Lib/asyncio/exceptions.py b/Lib/asyncio/exceptions.py index b13ffc1de8a8ee..c10ace6aa5be06 100644 --- a/Lib/asyncio/exceptions.py +++ b/Lib/asyncio/exceptions.py @@ -11,13 +11,8 @@ class CancelledError(BaseException): """The Future or Task was cancelled.""" -# GH-124308, BPO-32413: Originally, asyncio.TimeoutError was it's -# own unique exception. This was a bit of a gotcha because catching TimeoutError -# didn't catch asyncio.TimeoutError. So, it was turned into an alias -# of TimeoutError directly. Unfortunately, this made it effectively -# impossible to differentiate between asyncio.TimeoutError or a TimeoutError -# raised by a third party, so this is now a unique exception again (that inherits -# from TimeoutError to address the first problem). +# GH-124308, BPO-32413: Catching TimeoutError should catch asyncio.TimeoutError, but +# not vice versa. class TimeoutError(TimeoutError): """Operation timed out.""" From 58ed192f2b8de54a0d79c355da8618dcfd924b26 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 16:00:33 -0400 Subject: [PATCH 13/17] Unify comment. --- Lib/concurrent/futures/_base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index 4faed162142dcd..c733ed6c4f1a82 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -42,7 +42,8 @@ class CancelledError(Error): """The Future was cancelled.""" pass -# See GH-124308 +# GH-124308, BPO-42413: Catching TimeoutError should catch futures.TimeoutError, but +# not vice versa. class TimeoutError(TimeoutError): pass From 32ca14d33dc97d919ee6a2747538a4c6ceeaed4e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 16:05:27 -0400 Subject: [PATCH 14/17] Add NEWS entry. --- .../next/Library/2024-09-22-16-05-21.gh-issue-124308.EPJqAB.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-09-22-16-05-21.gh-issue-124308.EPJqAB.rst diff --git a/Misc/NEWS.d/next/Library/2024-09-22-16-05-21.gh-issue-124308.EPJqAB.rst b/Misc/NEWS.d/next/Library/2024-09-22-16-05-21.gh-issue-124308.EPJqAB.rst new file mode 100644 index 00000000000000..66e4b91accdfa3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-22-16-05-21.gh-issue-124308.EPJqAB.rst @@ -0,0 +1,2 @@ +Prevent :exc:`TimeoutError` from being caught when catching +:exc:`asyncio.TimeoutError` or :exc:`concurrent.futures.TimeoutError`. From a740f1e83fa89f1a28180e1030e93c3dd2a16c36 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 16:09:48 -0400 Subject: [PATCH 15/17] Fix indentation in concurrent.futures documentation. --- Doc/library/concurrent.futures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index fd6404aa913b55..593d511abd4f7d 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -549,7 +549,7 @@ Exception classes This class was made an alias of :exc:`TimeoutError`. - .. versionchanged:: 3.14 + .. versionchanged:: 3.14 This class was made a unique subclass of :exc:`TimeoutError`. From 1513eedc32c473410d3cf2a350498b322d22f27a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 16:16:28 -0400 Subject: [PATCH 16/17] Fix multiprocessing tests. --- Lib/test/_test_multiprocessing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 1d7e43b1cb64fb..a30eac4bc253cf 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -25,6 +25,7 @@ import pickle import weakref import warnings +import contextlib import test.support import test.support.script_helper from test import support @@ -2612,7 +2613,7 @@ def test_async_timeout(self): # GH-124308: Catching multiprocessing.TimeoutError should not catch TimeoutError with self.assertRaises(TimeoutError): - with suppress(multiprocessing.TimeoutError): + with contextlib.suppress(multiprocessing.TimeoutError): raise TimeoutError finally: if event is not None: From 6d9c5243e27c91abb5d3f5d516de0433b6f8ef90 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Sep 2024 16:29:09 -0400 Subject: [PATCH 17/17] Loosen TimeoutError check. --- Lib/test/test_asyncio/test_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index a1013ab803348d..b33bcf6fc8d27f 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -3502,7 +3502,7 @@ def test_run_coroutine_threadsafe_with_timeout(self): when a timeout is raised.""" callback = lambda: self.target(timeout=0) future = self.loop.run_in_executor(None, callback) - with self.assertRaises(asyncio.TimeoutError): + with self.assertRaises(TimeoutError): self.loop.run_until_complete(future) test_utils.run_briefly(self.loop) # Check that there's no pending task (add has been cancelled)