Skip to content

feat(io)!: registered files for compio sockets on io_uring - #153

Merged
lexoliu merged 1 commit into
mainfrom
feat/125-registered-files
Nov 11, 2026
Merged

lexoliu merged 1 commit into
mainfrom
feat/125-registered-files

Conversation

@lexoliu

@lexoliu lexoliu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

On Linux, each compio stream now takes a slot in its ring's registered file table. Every socket operation goes through that slot, so the kernel skips the file refcount on each operation. That covers receive (multishot and single), send, vectored and zero-copy send, write_control, the splice readiness polls and shutdown. Kernel-TLS setsockopt, address lookups and splice itself still use the plain descriptor.

  • compio::FileTable is the table size, an operator setting shared across clones. It defaults to 1024, the kernel's default soft open-file limit. A ring sizes its table when its first stream registers, so a new size only affects rings that register later. 0 stops new streams from taking slots straight away.
  • Compio::with_file_table and Compio::file_table carry it. with_zero_copy is now a builder method too: Compio::default().with_zero_copy(..).
  • A background task installs the slot with FILES_UPDATE, so operations never wait for it, and dropping the stream gives the slot back. A full table, or a kernel older than 5.19, leaves the stream on its plain descriptor. Any other registration failure comes back as the stream's next I/O error.

compio only registers a whole table at once and its own ops use the plain fd. This crate installs single slots with its own FilesUpdate op, and swaps in the slot index by writing the SQE the same way compio's set_poll_first does.

On Linux, new tests point a stream's fd at /dev/null after it registers and check that reads, copying writes, zero-copy writes and shutdown still reach the peer. They also cover a one-slot table falling back for the second socket, and a table size the kernel refuses failing the stream. The io, h1 and h3 builds pass. The kTLS suites need CI.

Fixed buffers are not part of this. See #125.

Part of #125

Each compio stream on Linux takes a slot in its ring's file table, and
every operation on it (multishot and single receives, copying and
zero-copy sends, readiness polls, shutdown) addresses the socket by that
slot with IOSQE_FIXED_FILE, so the kernel no longer looks the descriptor
up and references its file per operation. The descriptor stays for
kernel TLS options, addresses and splice.

The slot is allocated with IORING_OP_FILES_UPDATE on a runtime task, so
no operation waits for it and none abandoned midway loses it; the first
stream on a ring registers the table, sized by the new FileTable setting
(runtime-adjustable, default 1024). A full table or a kernel without slot
allocation leaves the stream on its descriptor; a table the kernel
refuses is the stream's next error. compio builds its operations on
descriptors, so an adapter rewrites the entries they build and keeps the
rest of each operation, buffer-ring handling included, compio's.

Compio::with_zero_copy becomes a builder method beside
Compio::with_file_table.

Refs #125
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 83.49515% with 51 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
io/src/compio.rs 83.49% 51 Missing ⚠️

📢 Thoughts on this report? Let us know!

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