Skip to content

Detach socket on create_connection cancellation to prevent fd double-close#740

Open
junjzhang wants to merge 5 commits intoMagicStack:masterfrom
junjzhang:fix/detach-socket-on-cancellation
Open

Detach socket on create_connection cancellation to prevent fd double-close#740
junjzhang wants to merge 5 commits intoMagicStack:masterfrom
junjzhang:fix/detach-socket-on-cancellation

Conversation

@junjzhang
Copy link
Copy Markdown

Summary

Fixes #738.

When create_connection(sock=sock) or create_unix_connection(sock=sock) is cancelled or raises, tr._close() closes the fd via libuv but the Python socket object still believes it owns that fd. Its __del__ later closes whatever fd the OS recycled into that slot, silently corrupting an unrelated transport.

This adds sock.detach() on the error path of both create_connection and create_unix_connection, matching the semantics of standard asyncio where the transport always takes full ownership of the socket.

Changes

  • loop.pyx: Call sock.detach() after tr._close() in the except BaseException block for both TCP and Unix sock= paths
  • test_tcp.py: Add test_create_connection_sock_cancel_detaches verifying that sock.fileno() == -1 after cancellation

When create_connection(sock=sock) or create_unix_connection(sock=sock) is
cancelled or raises, tr._close() closes the fd via libuv but the Python
socket object still believes it owns that fd number. Its __del__ later
closes whatever fd the OS recycled into that slot, corrupting unrelated
transports.

Call sock.detach() on the error path so the Python socket sets its
internal fd to -1, matching the semantics of standard asyncio where the
transport always takes full ownership of the socket.

Fixes MagicStack#738
@junjzhang
Copy link
Copy Markdown
Author

@1st1 Please have a look! thx

@6matt
Copy link
Copy Markdown

6matt commented Apr 14, 2026

@fantix could you review? this seems like a critical issue under high concurrency

@tarasko
Copy link
Copy Markdown
Contributor

tarasko commented Apr 16, 2026

Hey @junjzhang @6matt, perhaps one option for you would be to switch to aiofastnet? Not only it is faster, its networking source code is following python 3.14 asyncio implementation, which doesn't have this bug.

And if there is any other bug, I can fix it and release much faster.

I waited for more than a year to get some of my uvloop PRs merged, and they haven't been released yet.

Just saying

@jauntysankey
Copy link
Copy Markdown

I think this is a duplicate of #646

- Move test_create_connection_sock_cancel_detaches to _TestTCP so it
  runs on both uvloop and asyncio
- Add test_create_connection_sock_cancel_fd_leak that reproduces the
  full data leak chain: cancel → fd reuse → stale close → writev to
  wrong socket (see MagicStack#645, aio-libs/aiohttp#10506)
- Fix ConnectionAbortedError in detach test server handler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fantix
Copy link
Copy Markdown
Member

fantix commented Apr 30, 2026

I think this is a duplicate of #646

Good call! Though, #646 fixed it in a way that breaks asyncio compatibility. I think this PR is a better approach. Let me also add a theoretical test to reproduce #645, as well as aio-libs/aiohttp#10506.

Copy link
Copy Markdown
Member

@fantix fantix left a comment

Choose a reason for hiding this comment

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

@1st1 wdyt?

@fantix fantix force-pushed the fix/detach-socket-on-cancellation branch from 6d9acea to 9cc1d79 Compare April 30, 2026 17:31
Mirror the TCP cancel/detach and data-leak tests for the
create_unix_connection(sock=) path, covering the fix in
both create_connection and create_unix_connection.

Also fix server handlers to close writers (Python 3.12+
wait_closed() blocks until all connections are closed) and
fix flake8 blank line issue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fantix fantix force-pushed the fix/detach-socket-on-cancellation branch from 9cc1d79 to 74c24f1 Compare April 30, 2026 20:59
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.

create_connection(sock=sock) does not detach socket on cancellation, causing fd double-close

5 participants