Skip to content

don't silently discard UDP errors - #1295

Draft
graingert wants to merge 16 commits into
agronholm:masterfrom
graingert:error-recieved-udp
Draft

don't silently discard UDP errors#1295
graingert wants to merge 16 commits into
agronholm:masterfrom
graingert:error-recieved-udp

Conversation

@graingert

Copy link
Copy Markdown
Collaborator

NOTE Erasing or replacing the contents of this template will result in your pull
request being summarily closed without consideration!

Changes

Fixed UDP sockets on the asyncio backend silently discarding errors reported by the OS (such as an ICMP port unreachable). A task blocked in receive() or send() is now woken up by such an error, and both raise BrokenResourceError, chained to the original error, as they already did on the Trio backend. As on the Trio backend, the error is only reported once, leaving the socket usable afterwards

Checklist

If this is a user-facing code change, like a bugfix or a new feature, please ensure that
you've fulfilled the following conditions (where applicable):

  • You've added tests (in tests/) which would fail without your patch
  • You've updated the documentation (in docs/), in case of behavior changes or new
    features
  • You've added a new changelog entry (in docs/versionhistory.rst).

If this is a trivial change, like a typo fix or a code reformatting, then you can ignore
these instructions.

Updating the changelog

If there are no entries after the last release, use **UNRELEASED** as the version.
If, say, your patch fixes issue #123, the entry should look like this:

- Fix big bad boo-boo in task groups
  (`#123 <https://github.com/agronholm/anyio/issues/123>`_; PR by @yourgithubaccount)

If there's no issue linked, just link to your pull request instead by updating the
changelog after you've created the PR.

@graingert
graingert marked this pull request as ready for review August 26, 2026 09:10
@graingert
graingert marked this pull request as draft August 26, 2026 09:14
@graingert
graingert force-pushed the error-recieved-udp branch 2 times, most recently from c48ad66 to 430c246 Compare August 26, 2026 09:43
@graingert
graingert marked this pull request as ready for review August 26, 2026 16:18
@graingert
graingert requested a review from agronholm August 26, 2026 16:18
@graingert

Copy link
Copy Markdown
Collaborator Author

will need to be rebased on #1289 and #1294

@graingert
graingert marked this pull request as draft August 27, 2026 07:42
@graingert
graingert force-pushed the error-recieved-udp branch 2 times, most recently from 99dc210 to 6523920 Compare August 27, 2026 12:41
Errors reported by the transport overwrote each other, so a burst of OS
errors lost all but the last one. Queue them instead, popping the oldest
first, and only restore the write back-pressure once the queue drains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
agronholm and others added 12 commits August 29, 2026 01:16
A send() cancelled while blocked on the post-send wait leaves its datagram
in the asyncio transport's buffer with the protocol still paused. Without a
wait before sendto(), the next send() hands its datagram straight to the
transport, which appends it to that non-empty buffer without ever trying the
OS, so repeated cancellation grows the buffer despite the zero high water
mark.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
error_received() sets the write event while the transport is still paused, so
that a sender blocked on it can raise the error. If a concurrent receive()
claims that error first, take_exception() re-clears the event, but a sender
already released from the pre-send wait didn't re-check: it went on to hand its
datagram to a paused transport, which only appends it to its buffer without
ever offering it to the OS.

Guard the pre-send wait with the same write_paused loop the post-send wait
uses, so the pause flag, rather than the event, decides when it's safe to send.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	src/anyio/_backends/_asyncio.py
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unlike Trio, which runs a cancel_shielded_checkpoint() on the way out of a
failed socket call, the closed and broken paths now only get the
checkpoint_if_cancelled() at the top.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	src/anyio/_backends/_asyncio.py
@agronholm agronholm added this to the 4.15 milestone Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants