GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Don't report queued datagrams to the protocol after UDPTransport.abort() by graingert · Pull Request #772 · MagicStack/uvloop

Conversation

@graingert

@graingert graingert commented Oct 5, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Fixes #771.

Calling abort() on a UDPTransport with a datagram queued (the OS refused it with EAGAIN) reported two errors to the loop's exception handler. Vanilla asyncio silently discards the datagram.

When the handle closes, libuv cancels queued sends with UV_ECANCELED. The send callback turned that into a CancelledError "Fatal write error", then called _maybe_resume_protocol() after the protocol had already been cleared. That raised the resume_writing() AttributeError.

A second difference turned up while fixing this. A queued datagram can still be sent between abort() and the scheduled connection_lost(). uvloop then called resume_writing() on the aborted protocol, which asyncio never does, because its _force_close clears the buffer.

Changes:

  • __uv_udp_on_send drops sends that were cancelled because the handle is closed.
  • UDPTransport._on_sent reports nothing to the protocol once _conn_lost is set, i.e. after abort() or a fatal error. A graceful close() still flushes the queue and resumes the protocol, as asyncio does.
  • UDPTransport._close stops receiving, as UVStream._close does. The transport holds a reference to itself while receiving, so a UDP transport still open at loop.close() was never freed. The new loop-close test caught this through the debug build's handle-count check.

Tests run on both uvloop and asyncio. They use a UNIX datagram socket, because UDP over loopback never refuses a datagram. The asyncio variants are skipped on macOS: there the socket refuses datagrams with ENOBUFS, which libuv treats like EAGAIN and queues, but asyncio reports to error_received() and drops.

Tests:

  • test_abort_with_queued_datagram: the case from the issue.

  • test_abort_with_queued_datagram_then_writable: the peer makes room before the loop runs again, and there is no resume_writing() after abort().

  • test_close_with_queued_datagram_then_writable: close() behaviour is unchanged.

  • test_loop_close_with_queued_datagram (uvloop only): closing the loop with the transport still open cancels the queued send. On master this reports a fatal CancelledError and calls resume_writing(). The test covers the UV_ECANCELED check, which the abort tests don't reach because _conn_lost is already set.

All four tests fail on uvloop without this change.

🤖 Generated with Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
graingert and others added 6 commits October 5, 2026 10:54
@graingert @claude
Don't report queued datagrams to the protocol after UDPTransport.abort()
0e43341
Aborting a UDPTransport while a datagram was queued reported a fatal
CancelledError and a resume_writing() AttributeError to the loop's
exception handler, because libuv cancels queued sends when the handle
closes and the send callback treated that as an error after the
protocol had been detached.

Also, a queued datagram that was sent between abort() and the
transport closing still resumed the protocol. Like asyncio, stop
reporting send results to the protocol once the connection is lost.

Fixes MagicStack#771

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@graingert @claude
Test loop.close() with a queued datagram; drop unneeded sleep
b4c4762
The UV_ECANCELED check in the send callback was only exercised by
closing the loop with a UDP transport still open, as abort() is now
handled by the _conn_lost check in _on_sent.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@graingert @claude
Close the test sockets with a with block
fd02ac3
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@graingert @claude
Stop receiving when a UDPTransport handle is closed directly
6bd03ac
UDPTransport keeps a reference to itself while receiving, which was
only dropped by _stop_reading(). loop.close() closes leftover handles
with _close(), so a UDP transport still open at that point was never
freed. Stop reading in _close(), as UVStream does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@graingert @claude
Make the queued datagram tests terminate on macOS
9a0faf8
The tests sent datagrams until the transport queued one, which never
happened on macOS CI and spun until the job was killed. Use a small
send buffer, as anyio's UNIX datagram tests do, and give up after a
bounded number of sends.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@graingert @claude
Skip the asyncio queued datagram tests on macOS
26651d5
macOS refuses UNIX datagrams with ENOBUFS rather than EAGAIN. libuv
treats ENOBUFS like EAGAIN and queues the datagram, but asyncio reports
it to error_received() and drops it, so the send queue never fills.
The small send buffer did not change that, so drop it again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

UDPTransport.abort() with a queued datagram reports a fatal write error and a resume_writing() AttributeError

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@graingert
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.