Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-34622: Extract asyncio exceptions into a separate module #9141

Merged
merged 5 commits into from
Sep 11, 2018
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
2 changes: 2 additions & 0 deletions Lib/asyncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .base_events import *
from .coroutines import *
from .events import *
from .exceptions import *
from .futures import *
from .locks import *
from .protocols import *
Expand All @@ -25,6 +26,7 @@
__all__ = (base_events.__all__ +
coroutines.__all__ +
events.__all__ +
exceptions.__all__ +
futures.__all__ +
locks.__all__ +
protocols.__all__ +
Expand Down
11 changes: 6 additions & 5 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from . import constants
from . import coroutines
from . import events
from . import exceptions
from . import futures
from . import protocols
from . import sslproto
Expand Down Expand Up @@ -327,7 +328,7 @@ async def serve_forever(self):

try:
await self._serving_forever_fut
except futures.CancelledError:
except exceptions.CancelledError:
try:
self.close()
await self.wait_closed()
Expand Down Expand Up @@ -800,7 +801,7 @@ async def sock_sendfile(self, sock, file, offset=0, count=None,
try:
return await self._sock_sendfile_native(sock, file,
offset, count)
except events.SendfileNotAvailableError as exc:
except exceptions.SendfileNotAvailableError as exc:
if not fallback:
raise
return await self._sock_sendfile_fallback(sock, file,
Expand All @@ -809,7 +810,7 @@ async def sock_sendfile(self, sock, file, offset=0, count=None,
async def _sock_sendfile_native(self, sock, file, offset, count):
# NB: sendfile syscall is not supported for SSL sockets and
# non-mmap files even if sendfile is supported by OS
raise events.SendfileNotAvailableError(
raise exceptions.SendfileNotAvailableError(
f"syscall sendfile is not available for socket {sock!r} "
"and file {file!r} combination")

Expand Down Expand Up @@ -1053,7 +1054,7 @@ async def sendfile(self, transport, file, offset=0, count=None,
try:
return await self._sendfile_native(transport, file,
offset, count)
except events.SendfileNotAvailableError as exc:
except exceptions.SendfileNotAvailableError as exc:
if not fallback:
raise

Expand All @@ -1066,7 +1067,7 @@ async def sendfile(self, transport, file, offset=0, count=None,
offset, count)

async def _sendfile_native(self, transp, file, offset, count):
raise events.SendfileNotAvailableError(
raise exceptions.SendfileNotAvailableError(
"sendfile syscall is not supported")

async def _sendfile_fallback(self, transp, file, offset, count):
Expand Down
6 changes: 0 additions & 6 deletions Lib/asyncio/base_futures.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
__all__ = ()

import concurrent.futures
import reprlib

from . import format_helpers

CancelledError = concurrent.futures.CancelledError
TimeoutError = concurrent.futures.TimeoutError
InvalidStateError = concurrent.futures.InvalidStateError


# States for Future.
_PENDING = 'PENDING'
_CANCELLED = 'CANCELLED'
Expand Down
11 changes: 2 additions & 9 deletions Lib/asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
__all__ = (
'AbstractEventLoopPolicy',
'AbstractEventLoop', 'AbstractServer',
'Handle', 'TimerHandle', 'SendfileNotAvailableError',
'Handle', 'TimerHandle',
'get_event_loop_policy', 'set_event_loop_policy',
'get_event_loop', 'set_event_loop', 'new_event_loop',
'get_child_watcher', 'set_child_watcher',
Expand All @@ -19,14 +19,7 @@
import threading

from . import format_helpers


class SendfileNotAvailableError(RuntimeError):
"""Sendfile syscall is not available.

Raised if OS does not support sendfile syscall for given socket or
file type.
"""
from . import exceptions


class Handle:
Expand Down
60 changes: 60 additions & 0 deletions Lib/asyncio/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""asyncio exceptions."""


__all__ = ('CancelledError', 'InvalidStateError', 'TimeoutError',
'IncompleteReadError', 'LimitOverrunError',
'SendfileNotAvailableError')

import concurrent.futures
from . import base_futures


class CancelledError(concurrent.futures.CancelledError):
Copy link
Member

Choose a reason for hiding this comment

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

Why define subclasses instead of just making aliases?

Copy link
Member

Choose a reason for hiding this comment

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

The main motivation is to improve usability. There's no fundamental reason why these exceptions are derived from concurrent.future, it happened pretty much by accident. But the problem is that asyncio users see concurrent.future.TimeoutError exceptions occurring in their programs and have no idea why it's not asyncio.TimeoutError.

Moreover, we have a plan to make asyncio.CancelledError a BaseException in a follow up PR.

"""The Future or Task was cancelled."""


class TimeoutError(concurrent.futures.TimeoutError):
"""The operation exceeded the given deadline."""


class InvalidStateError(concurrent.futures.InvalidStateError):
"""The operation is not allowed in this state."""


class SendfileNotAvailableError(RuntimeError):
"""Sendfile syscall is not available.

Raised if OS does not support sendfile syscall for given socket or
file type.
"""


class IncompleteReadError(EOFError):
"""
Incomplete read error. Attributes:

- partial: read bytes string before the end of stream was reached
- expected: total number of expected bytes (or None if unknown)
"""
def __init__(self, partial, expected):
super().__init__(f'{len(partial)} bytes read on a total of '
f'{expected!r} expected bytes')
self.partial = partial
self.expected = expected

def __reduce__(self):
return type(self), (self.partial, self.expected)


class LimitOverrunError(Exception):
"""Reached the buffer limit while looking for a separator.

Attributes:
- consumed: total number of to be consumed bytes.
"""
def __init__(self, message, consumed):
super().__init__(message)
self.consumed = consumed

def __reduce__(self):
return type(self), (self.args[0], self.consumed)
33 changes: 21 additions & 12 deletions Lib/asyncio/futures.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""A Future class similar to the one in PEP 3148."""

__all__ = (
'CancelledError', 'TimeoutError', 'InvalidStateError',
'Future', 'wrap_future', 'isfuture',
)

Expand All @@ -12,12 +11,10 @@

from . import base_futures
from . import events
from . import exceptions
from . import format_helpers


CancelledError = base_futures.CancelledError
InvalidStateError = base_futures.InvalidStateError
TimeoutError = base_futures.TimeoutError
isfuture = base_futures.isfuture


Expand Down Expand Up @@ -170,9 +167,9 @@ def result(self):
the future is done and has an exception set, this exception is raised.
"""
if self._state == _CANCELLED:
raise CancelledError
raise exceptions.CancelledError
if self._state != _FINISHED:
raise InvalidStateError('Result is not ready.')
raise exceptions.InvalidStateError('Result is not ready.')
self.__log_traceback = False
if self._exception is not None:
raise self._exception
Expand All @@ -187,9 +184,9 @@ def exception(self):
InvalidStateError.
"""
if self._state == _CANCELLED:
raise CancelledError
raise exceptions.CancelledError
if self._state != _FINISHED:
raise InvalidStateError('Exception is not set.')
raise exceptions.InvalidStateError('Exception is not set.')
self.__log_traceback = False
return self._exception

Expand Down Expand Up @@ -231,7 +228,7 @@ def set_result(self, result):
InvalidStateError.
"""
if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self))
raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
self._result = result
self._state = _FINISHED
self.__schedule_callbacks()
Expand All @@ -243,7 +240,7 @@ def set_exception(self, exception):
InvalidStateError.
"""
if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self))
raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
if isinstance(exception, type):
exception = exception()
if type(exception) is StopIteration:
Expand Down Expand Up @@ -288,6 +285,18 @@ def _set_result_unless_cancelled(fut, result):
fut.set_result(result)


def _convert_future_exc(exc):
exc_class = type(exc)
if exc_class is concurrent.futures.CancelledError:
return exceptions.CancelledError(*exc.args)
elif exc_class is concurrent.futures.TimeoutError:
return exceptions.TimeoutError(*exc.args)
elif exc_class is concurrent.futures.InvalidStateError:
return exceptions.InvalidStateError(*exc.args)
else:
return exc


def _set_concurrent_future_state(concurrent, source):
"""Copy state from a future to a concurrent.futures.Future."""
assert source.done()
Expand All @@ -297,7 +306,7 @@ def _set_concurrent_future_state(concurrent, source):
return
exception = source.exception()
if exception is not None:
concurrent.set_exception(exception)
concurrent.set_exception(_convert_future_exc(exception))
else:
result = source.result()
concurrent.set_result(result)
Expand All @@ -317,7 +326,7 @@ def _copy_future_state(source, dest):
else:
exception = source.exception()
if exception is not None:
dest.set_exception(exception)
dest.set_exception(_convert_future_exc(exception))
else:
result = source.result()
dest.set_result(result)
Expand Down
7 changes: 4 additions & 3 deletions Lib/asyncio/locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from . import events
from . import futures
from . import exceptions
from .coroutines import coroutine


Expand Down Expand Up @@ -192,7 +193,7 @@ async def acquire(self):
await fut
finally:
self._waiters.remove(fut)
except futures.CancelledError:
except exceptions.CancelledError:
if not self._locked:
self._wake_up_first()
raise
Expand Down Expand Up @@ -363,11 +364,11 @@ async def wait(self):
try:
await self.acquire()
break
except futures.CancelledError:
except exceptions.CancelledError:
cancelled = True

if cancelled:
raise futures.CancelledError
raise exceptions.CancelledError

async def wait_for(self, predicate):
"""Wait until a predicate becomes true.
Expand Down
11 changes: 6 additions & 5 deletions Lib/asyncio/proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from . import constants
from . import events
from . import futures
from . import exceptions
from . import protocols
from . import sslproto
from . import transports
Expand Down Expand Up @@ -282,7 +283,7 @@ def _loop_reading(self, fut=None):
self._force_close(exc)
except OSError as exc:
self._fatal_error(exc, 'Fatal read error on pipe transport')
except futures.CancelledError:
except exceptions.CancelledError:
if not self._closing:
raise
else:
Expand Down Expand Up @@ -555,11 +556,11 @@ async def _sock_sendfile_native(self, sock, file, offset, count):
try:
fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err:
raise events.SendfileNotAvailableError("not a regular file")
raise exceptions.SendfileNotAvailableError("not a regular file")
try:
fsize = os.fstat(fileno).st_size
except OSError as err:
raise events.SendfileNotAvailableError("not a regular file")
raise exceptions.SendfileNotAvailableError("not a regular file")
blocksize = count if count else fsize
if not blocksize:
return 0 # empty file
Expand Down Expand Up @@ -615,7 +616,7 @@ def _loop_self_reading(self, f=None):
if f is not None:
f.result() # may raise
f = self._proactor.recv(self._ssock, 4096)
except futures.CancelledError:
except exceptions.CancelledError:
# _close_self_pipe() has been called, stop waiting for data
return
except Exception as exc:
Expand Down Expand Up @@ -666,7 +667,7 @@ def loop(f=None):
elif self._debug:
logger.debug("Accept failed on socket %r",
sock, exc_info=True)
except futures.CancelledError:
except exceptions.CancelledError:
sock.close()
else:
self._accept_futures[sock.fileno()] = f
Expand Down
Loading