Skip to content
Open
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 newsfragments/3509.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
On Windows, :func:`trio.lowlevel.wait_readable`, :func:`trio.lowlevel.wait_writable`
and :func:`trio.lowlevel.notify_closing` raised ``BusyResourceError`` and
``ClosedResourceError`` without any message. They now explain themselves, as they
already did on the epoll and kqueue backends.
9 changes: 7 additions & 2 deletions src/trio/_core/_io_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,9 @@ async def _afd_poll(self, sock: _HasFileNo | int, mode: str) -> None:
waiters = AFDWaiters()
self._afd_waiters[base_handle] = waiters
if getattr(waiters, mode) is not None:
raise _core.BusyResourceError
raise _core.BusyResourceError(
"another task is already reading / writing this socket",
)
setattr(waiters, mode, _core.current_task())
# Could potentially raise if the handle is somehow invalid; that's OK,
# we let it escape.
Expand Down Expand Up @@ -822,7 +824,10 @@ def notify_closing(self, handle: Handle | int | _HasFileNo) -> None:
handle = _get_base_socket(handle)
waiters = self._afd_waiters.get(handle)
if waiters is not None:
wake_all(waiters, _core.ClosedResourceError())
wake_all(
waiters,
_core.ClosedResourceError("another task closed this socket"),
)
self._refresh_afd(handle)

################################################################
Expand Down
20 changes: 16 additions & 4 deletions src/trio/_core/_tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ async def block_on_write() -> None:
assert record == ["cancelled"]


# The two real BusyResourceError texts from the three backends: epoll and
# windows word it one way, kqueue another. Matching both asserts the message is
# one of them rather than merely non-empty.
BUSY_MESSAGE = r"already reading / writing this|multiple listeners for same ident"


@read_socket_test
async def test_double_read(socketpair: SocketPair, wait_readable: WaitSocket) -> None:
a, _b = socketpair
Expand All @@ -170,7 +176,8 @@ async def test_double_read(socketpair: SocketPair, wait_readable: WaitSocket) ->
async with _core.open_nursery() as nursery:
nursery.start_soon(wait_readable, a)
await wait_all_tasks_blocked()
with pytest.raises(_core.BusyResourceError):
# epoll and windows say one thing here, kqueue another, so match both.
with pytest.raises(_core.BusyResourceError, match=BUSY_MESSAGE):
await wait_readable(a)
nursery.cancel_scope.cancel()

Expand All @@ -184,7 +191,8 @@ async def test_double_write(socketpair: SocketPair, wait_writable: WaitSocket) -
async with _core.open_nursery() as nursery:
nursery.start_soon(wait_writable, a)
await wait_all_tasks_blocked()
with pytest.raises(_core.BusyResourceError):
# epoll and windows say one thing here, kqueue another, so match both.
with pytest.raises(_core.BusyResourceError, match=BUSY_MESSAGE):
await wait_writable(a)
nursery.cancel_scope.cancel()

Expand All @@ -201,11 +209,15 @@ async def test_interrupted_by_close(
a, _b = socketpair

async def reader() -> None:
with pytest.raises(_core.ClosedResourceError):
with pytest.raises(
_core.ClosedResourceError, match="another task closed this "
):
await wait_readable(a)

async def writer() -> None:
with pytest.raises(_core.ClosedResourceError):
with pytest.raises(
_core.ClosedResourceError, match="another task closed this "
):
await wait_writable(a)

fill_socket(a)
Expand Down
Loading