-
Notifications
You must be signed in to change notification settings - Fork 180
Description
Things to check first
-
I have searched the existing issues and didn't find my bug already reported there
-
I have checked that my bug is still present in the latest release
AnyIO version
4.11.0
Python version
3.11.2
What happened?
9a464bf updated the Asyncio SocketStream receive exception handling to raise self._protocol.exception from None. Raising from None suppresses the cause of the exception, so users of the asyncio SocketStream are not able to differentiate the underlying cause of the BrokenResourceError, e.g. library users may want to implement specific handling for different types of ConnectionErrors:
ConnectionAbortedErrorConnectionRefusedErrorConnectionResetError
Using the trio backend, the SocketStream maintains the exception cause (which is preferable IMO) so the two backend implementations are not at parity in this aspect.
How can we reproduce the bug?
The following example should raise a ConnectionResetError on Linux, though the exception cause is lost as the exception.__cause__ is set to None. Using the trio backend, the ConnectionResetError can still be found in the cause of the BrokenResourcError
import socket
import struct
import anyio
from anyio.abc import SocketStream
async def handler(socket: SocketStream) -> None:
try:
async for _ in socket:
...
except anyio.BrokenResourceError as exc:
print(type(exc.__cause__))
raise
async def main() -> None:
server = await anyio.create_tcp_listener(local_port=12345)
async with anyio.create_task_group() as tg:
tg.start_soon(server.serve, handler)
s = socket.socket()
s.connect(("localhost", 12345))
# Create a ConnectionResetError
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
s.close()
anyio.run(main, backend="asyncio")Asyncio traceback snippet:
<class 'NoneType'>
...
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/user/SW/anyio/socket_test.py", line 12, in handler
| async for _ in socket:
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/anyio/abc/_streams.py", line 131, in __anext__
| return await self.receive()
| ^^^^^^^^^^^^^^^^^^^^
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 1275, in receive
| raise self._protocol.exception from None
| anyio.BrokenResourceError
Trio traceback snippet:
<class 'ConnectionResetError'>
...
| ExceptionGroup: Exceptions from Trio nursery (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/anyio/_backends/_trio.py", line 388, in receive
| data = await self._trio_socket.recv(max_bytes)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/trio/_socket.py", line 440, in wrapper
| return await self._nonblocking_helper(wait_fn, fn, *args, **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/trio/_socket.py", line 1014, in _nonblocking_helper
| return fn(self._sock, *args, **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ConnectionResetError: [Errno 104] Connection reset by peer
|
| The above exception was the direct cause of the following exception:
|
| Traceback (most recent call last):
| File "/home/user/SW/anyio/socket_test.py", line 10, in handler
| async for _ in socket:
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/anyio/abc/_streams.py", line 131, in __anext__
| return await self.receive()
| ^^^^^^^^^^^^^^^^^^^^
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/anyio/_backends/_trio.py", line 390, in receive
| self._convert_socket_error(exc)
| File "/home/user/SW/anyio/.venv/lib/python3.11/site-packages/anyio/_backends/_trio.py", line 374, in _convert_socket_error
| raise BrokenResourceError from exc
| anyio.BrokenResourceError
+------------------------------------