Skip to content
Closed
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
4 changes: 4 additions & 0 deletions Doc/library/asyncio-exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Exceptions

This class was made an alias of :exc:`TimeoutError`.

.. versionchanged:: 3.14

This class was made a unique subclass of :exc:`TimeoutError`.


.. exception:: CancelledError

Expand Down
6 changes: 5 additions & 1 deletion Doc/library/concurrent.futures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 a unique subclass of :exc:`TimeoutError`.


.. exception:: BrokenExecutor

Expand Down
4 changes: 4 additions & 0 deletions Doc/library/multiprocessing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^

Expand Down
5 changes: 4 additions & 1 deletion Lib/asyncio/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ class CancelledError(BaseException):
"""The Future or Task was cancelled."""


TimeoutError = TimeoutError # make local alias for the standard exception
# GH-124308, BPO-32413: Catching TimeoutError should catch asyncio.TimeoutError, but
# not vice versa.
class TimeoutError(TimeoutError):
"""Operation timed out."""


class InvalidStateError(Exception):
Expand Down
2 changes: 1 addition & 1 deletion Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Lib/asyncio/timeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
5 changes: 4 additions & 1 deletion Lib/concurrent/futures/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ class CancelledError(Error):
"""The Future was cancelled."""
pass

TimeoutError = TimeoutError # make local alias for the standard exception
# GH-124308, BPO-42413: Catching TimeoutError should catch futures.TimeoutError, but
# not vice versa.
class TimeoutError(TimeoutError):
pass

class InvalidStateError(Error):
"""The operation is not allowed in this state."""
Expand Down
2 changes: 1 addition & 1 deletion Lib/multiprocessing/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ProcessError(Exception):
class BufferTooShort(ProcessError):
pass

class TimeoutError(ProcessError):
class TimeoutError(ProcessError, TimeoutError):
pass

class AuthenticationError(ProcessError):
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import pickle
import weakref
import warnings
import contextlib
import test.support
import test.support.script_helper
from test import support
Expand Down Expand Up @@ -2605,6 +2606,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 contextlib.suppress(multiprocessing.TimeoutError):
raise TimeoutError
finally:
if event is not None:
event.set()
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_asyncio/test_timeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import asyncio

from contextlib import suppress
from test.test_asyncio.utils import await_without_task


Expand Down Expand Up @@ -406,6 +407,17 @@ async def task():
self.assertIsNone(e3.__cause__)
self.assertIs(e2.__context__, e3)

async def test_timeouterror_is_unique(self):
# BPO-42413: Catching TimeoutError should include asyncio.TimeoutError
with self.assertRaises(TimeoutError):
async with asyncio.timeout(0.01):
await asyncio.sleep(1)

with self.assertRaises(TimeoutError):
# GH-124308: Catching asyncio.TimeoutError should not include TimeoutError
with suppress(asyncio.TimeoutError):
raise TimeoutError


if __name__ == '__main__':
unittest.main()
12 changes: 11 additions & 1 deletion Lib/test/test_concurrent_futures/test_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
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

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)
Expand Down Expand Up @@ -196,6 +197,15 @@ 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):
raise futures.TimeoutError

with self.assertRaises(TimeoutError):
# GH-124308: Catching futures.TimeoutError should not catch TimeoutError
with suppress(futures.TimeoutError):
raise TimeoutError

def test_result_with_success(self):
# TODO(brian@sweetapp.com): This test is timing dependent.
def notification():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Prevent :exc:`TimeoutError` from being caught when catching
:exc:`asyncio.TimeoutError` or :exc:`concurrent.futures.TimeoutError`.
Loading