Skip to content

7960: feat: add SQPOLL support for io_uring#72

Open
martin-augment wants to merge 1 commit intomasterfrom
pr-7960-2026-03-09-06-01-03
Open

7960: feat: add SQPOLL support for io_uring#72
martin-augment wants to merge 1 commit intomasterfrom
pr-7960-2026-03-09-06-01-03

Conversation

@martin-augment
Copy link
Copy Markdown
Owner

7960: To review by AI

This adds `uring_setup_sqpoll` to the Runtime `Builder`, allowing the io_uring driver
to be configured with SQPOLL enabled. This offloads submission queue polling to a
kernel thread, which can significantly reduce syscall overhead.

- Added `uring_setup_sqpoll` to `tokio::runtime::Builder`.
- Updated `UringContext` to sync the submission queue and check `need_wakeup`.
- Added tests to verify `sqpoll` in both single-thread and multi-thread runtimes.
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances Tokio's io_uring support by adding the ability to enable and configure the SQPOLL feature. This allows the io_uring driver to utilize a dedicated kernel thread for polling the submission queue, which can significantly reduce syscall overhead and improve I/O performance for applications running on Linux with io_uring enabled.

Highlights

  • New io_uring SQPOLL Configuration: Introduced a new uring_setup_sqpoll method on the tokio::runtime::Builder to enable and configure the SQPOLL feature for io_uring drivers. This allows users to specify an idle timeout for the kernel submission queue polling thread.
  • Internal io_uring Driver Enhancements: Modified the internal io_uring driver to support SQPOLL, including storing the idle timeout, configuring the io_uring instance with SQPOLL during initialization, and optimizing the submission logic to leverage SQPOLL's kernel-side polling.
  • Runtime Builder Integration: Integrated the SQPOLL setup into both single-threaded and multi-threaded runtime builders, ensuring that the configured SQPOLL idle timeout is applied when the runtime is built.
  • New Test Coverage: Added new integration tests (fs_uring_sqpoll.rs) to verify the correct functionality of io_uring with SQPOLL enabled in both current-thread and multi-thread runtime configurations, performing basic file I/O operations.
Changelog
  • tokio/src/runtime/builder.rs
    • Added uring_setup_sqpoll field to the Builder struct.
    • Initialized uring_setup_sqpoll to None in the Builder::new constructor.
    • Implemented logic to call io.setup_uring_sqpoll if an idle timeout is configured during runtime build.
    • Exposed a public uring_setup_sqpoll method on the Builder for user configuration, including documentation and an example.
  • tokio/src/runtime/driver.rs
    • Refactored create_io_stack function signature to accept a &Cfg reference.
    • Updated calls to create_io_stack to pass the Cfg reference.
  • tokio/src/runtime/io/driver/uring.rs
    • Added sqpoll_idle field to the UringContext struct.
    • Initialized sqpoll_idle to None in UringContext::new.
    • Modified UringContext::init to conditionally build IoUring with SQPOLL based on sqpoll_idle.
    • Added SQPOLL-specific logic to UringContext::submit to sync the submission queue and avoid unnecessary wakeups.
    • Implemented setup_uring_sqpoll method on Handle to set the sqpoll_idle value in the UringContext.
  • tokio/tests/fs_uring_sqpoll.rs
    • Added a new test file for io_uring SQPOLL functionality.
    • Included test_sqpoll_current_thread to verify SQPOLL in a single-threaded runtime.
    • Included test_sqpoll_multi_thread to verify SQPOLL in a multi-threaded runtime.
Activity
  • The pull request was created by martin-augment.
  • The author has requested review by AI.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 9, 2026

Walkthrough

This pull request adds support for configuring SQ poll idle timeout in Tokio's io_uring driver. A new builder method uring_setup_sqpoll() is introduced to set the idle timeout value. The configuration flows through the runtime builder to the io driver during initialization. The create_io_stack() function signature is refactored to accept a configuration object instead of individual parameters. The UringContext is updated to store and conditionally apply the sqpoll idle setting when initializing the IoUring instance. Additionally, new tests verify the functionality for both single-thread and multi-thread runtime configurations.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr-7960-2026-03-09-06-01-03

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for SQPOLL in io_uring, which can improve performance by reducing syscall overhead. The changes introduce a new uring_setup_sqpoll method to the runtime Builder and correctly wire it through to the io_uring driver initialization. The implementation also includes logic to avoid unnecessary syscalls when SQPOLL is enabled. New integration tests are added to verify the functionality for both single-threaded and multi-threaded runtimes.

My main feedback is to address a small amount of code duplication in builder.rs to improve maintainability. Otherwise, the changes look good.

Comment on lines +1819 to +1824
#[cfg(all(tokio_unstable, feature = "io-uring", target_os = "linux"))]
if let Some(idle) = self.uring_setup_sqpoll {
if let Some(io) = driver_handle.io.as_ref() {
io.setup_uring_sqpoll(idle);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This block of code is identical to the one in build_current_thread_runtime_components (lines 1607-1612). To avoid duplication and improve maintainability, consider extracting this logic into a private helper function.

For example, you could add a helper method like this:

#[cfg(all(tokio_unstable, feature = "io-uring", target_os = "linux"))]
fn setup_uring_sqpoll(&self, driver_handle: &driver::Handle) {
    if let Some(idle) = self.uring_setup_sqpoll {
        if let Some(io) = driver_handle.io.as_ref() {
            io.setup_uring_sqpoll(idle);
        }
    }
}

And then call it from both build_current_thread_runtime_components and build_threaded_runtime.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

value:good-to-have; category:bug; feedback: The Gemini AI reviewer is correct! The same snippet of code is used in two places. It would be good to extract it to a helper function and reuse it. This would prevent any eventual regressions in the future if a bug is fixed in only one of the duplicated snippets.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tokio/src/runtime/builder.rs (1)

1758-1779: Documentation could mention privilege requirements and error behavior.

The SQPOLL feature typically requires elevated privileges (CAP_SYS_ADMIN or kernel 5.11+ with specific configs). Consider documenting:

  1. That SQPOLL may fail to initialize on systems without proper privileges
  2. What happens when initialization fails (falls back to regular io_uring or returns error?)
📝 Suggested documentation enhancement
         /// Enables SQPOLL for the io_uring driver and sets the idle timeout.
         ///
         /// When SQPOLL is enabled, a kernel thread is created to poll the
         /// submission queue. This can reduce syscall overhead.
         ///
+        /// # Requirements
+        ///
+        /// SQPOLL mode may require elevated privileges depending on kernel version.
+        /// On Linux 5.11+, unprivileged users may use SQPOLL if the system is configured
+        /// to allow it. On older kernels, `CAP_SYS_ADMIN` is typically required.
+        ///
+        /// If SQPOLL initialization fails due to permission issues, the runtime
+        /// will return an error during build.
+        ///
         /// # Examples
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tokio/src/runtime/builder.rs` around lines 1758 - 1779, The doc comment for
Builder::uring_setup_sqpoll should be expanded to mention that enabling SQPOLL
may require elevated privileges (e.g., CAP_SYS_ADMIN) or specific kernel
configuration (kernel ≥5.11+ and proper io_uring options) and to state the
observable behavior when SQPOLL initialization fails; update the docs on
uring_setup_sqpoll (and nearby enable_io_uring/build examples) to explicitly
note the privilege/kernel requirements and whether the runtime will fall back to
non-SQPOLL io_uring or cause build()/runtime initialization to return an error
so callers know what to expect at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tokio/src/runtime/builder.rs`:
- Around line 1758-1779: The doc comment for Builder::uring_setup_sqpoll should
be expanded to mention that enabling SQPOLL may require elevated privileges
(e.g., CAP_SYS_ADMIN) or specific kernel configuration (kernel ≥5.11+ and proper
io_uring options) and to state the observable behavior when SQPOLL
initialization fails; update the docs on uring_setup_sqpoll (and nearby
enable_io_uring/build examples) to explicitly note the privilege/kernel
requirements and whether the runtime will fall back to non-SQPOLL io_uring or
cause build()/runtime initialization to return an error so callers know what to
expect at runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 706acee0-06a2-4a20-9b8f-ee214e719a25

📥 Commits

Reviewing files that changed from the base of the PR and between 1104af3 and 69d5df8.

📒 Files selected for processing (4)
  • tokio/src/runtime/builder.rs
  • tokio/src/runtime/driver.rs
  • tokio/src/runtime/io/driver/uring.rs
  • tokio/tests/fs_uring_sqpoll.rs

@claude
Copy link
Copy Markdown

claude bot commented Mar 9, 2026

Contributing to Tokio

Thanks for your help improving Tokio! We are so happy to have you!

There are opportunities to contribute to Tokio at any level. It doesn't matter if
you are just getting started with Rust or are the most weathered expert, we can
use your help.

No contribution is too small and all contributions are valued.

See the contributing guidelines to get started.

Code of Conduct

The Tokio project adheres to the Rust Code of Conduct. This describes
the minimum behavior expected from all contributors. Instances of violations of the
Code of Conduct can be reported by contacting the project team at
moderation@tokio.rs.

Need Help?

Reach out to us on the Discord server for any concern not covered in this guide.

LTS guarantees

In Tokio ≥1.0.0, each LTS release comes with the guarantee of at least one year of
backported fixes.

The goal of these guarantees is to provide stability to the ecosystem.

Minimum Supported Rust Version (MSRV)

  • All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
    compiler release.
  • The MSRV will only be increased on 1.x releases.

Versioning Policy

With Tokio ≥1.0.0:

  • Patch (1._.x) releases should only contain bug fixes or documentation
    changes. Besides this, these releases should not substantially change
    runtime behavior.
  • Minor (1.x) releases may contain new functionality, MSRV increases (see
    above), minor dependency updates, deprecations, and larger internal
    implementation changes.

This is as defined by Semantic Versioning 2.0.

@claude
Copy link
Copy Markdown

claude bot commented Mar 9, 2026

test simple body again

@claude
Copy link
Copy Markdown

claude bot commented Mar 9, 2026

PR Review: Add SQPOLL support for io_uring

line2
line3

@augmentcode
Copy link
Copy Markdown

augmentcode bot commented Mar 9, 2026

🤖 Augment PR Summary

Summary: Adds optional SQPOLL configuration for Tokio’s io_uring-backed filesystem I/O on Linux (unstable), allowing users to enable a kernel SQ polling thread and set its idle timeout.

Changes:

  • Extends runtime::Builder with a new uring_setup_sqpoll(u32) knob (stored as an optional idle timeout).
  • Plumbs the SQPOLL setting into the runtime driver handle during runtime construction (current-thread and multi-thread).
  • Extends the io_uring driver context to optionally build rings with IORING_SETUP_SQPOLL and to optimize submission by skipping io_uring_enter when wakeup is not needed.
  • Refactors runtime driver initialization to pass the full driver config into create_io_stack (instead of individual flags).
  • Adds Linux-only integration tests that exercise file read/write/seek flows with SQPOLL enabled on both runtime flavors.

Technical Notes: The SQPOLL setup is applied prior to first io_uring initialization (which is lazy), and the submission path uses submission().sync() + need_wakeup() to avoid unnecessary syscalls when SQPOLL is active.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

cfg.timer_flavor = TimerFlavor::Traditional;
let (driver, driver_handle) = driver::Driver::new(cfg)?;

#[cfg(all(tokio_unstable, feature = "io-uring", target_os = "linux"))]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The cfg here doesn’t match the conditions under which the io_uring pieces (including setup_uring_sqpoll) are actually compiled (cfg_io_uring! also requires feature = "rt" and feature = "fs"). As written, building with tokio_unstable + io-uring on Linux but without fs can try to compile this call path and fail due to missing symbols.

Severity: high

Other Locations
  • tokio/src/runtime/builder.rs:1819

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

value:useful; category:bug; feedback: The Augment AI reviewer is correct! All io_uring related functionalities should be gated behind the same cfg!. Otherwise some methods may be visible in some config but not usable due to others being missing in this config.

}

let uring = IoUring::new(DEFAULT_RING_SIZE)?;
let uring = if let Some(idle) = self.sqpoll_idle {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

setup_sqpoll can fail with EPERM on systems that allow regular io_uring but don’t permit SQPOLL; since check_and_init treats EPERM as “io_uring unavailable”, this would disable io_uring entirely when SQPOLL is requested. Consider distinguishing SQPOLL permission errors from “io_uring blocked” errors so non-SQPOLL io_uring can still be used.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

value:valid-but-wont-fix; category:bug; feedback: The Augment AI reviewer is нот correct! If the SQPOLL setup fails then the error should be returned to the caller. Then it can try to setup io_uring without SQPOLL. The Tokio library should not try to do this automatically without letting know the developer.

@claude
Copy link
Copy Markdown

claude bot commented Mar 9, 2026

PR Review: Add SQPOLL support for io_uring

Overall, this is a solid feature addition. The SQPOLL submission optimization in submit() is correctly implemented per the io_uring SQPOLL spec. Here are the issues worth addressing:


Correctness Issues

1. Tests may not exercise io_uring or SQPOLL at all

The tests call enable_all() but not enable_io_uring(). Since enable_io_uring() is a separate opt-in method, these tests may only exercise the non-io_uring fallback path and never actually test SQPOLL. Call enable_io_uring() explicitly in the tests.

2. Tests do not handle SQPOLL unavailability

IORING_SETUP_SQPOLL requires CAP_SYS_ADMIN or CAP_SYS_NICE on kernels older than 5.11. In CI or sandboxed environments this fails with EPERM, causing .unwrap() to panic. Tests should detect this and skip gracefully.

3. Potential busy-spin during UringContext::Drop with SQPOLL

The existing Drop loop:

while !self.ring_mut().submission().is_empty() {
    self.submit().expect("Internal error when dropping driver");
}

When SQPOLL is active and need_wakeup() is false, the new submit() returns immediately after sync() without blocking. But is_empty() only becomes true once the kernel SQPOLL thread advances sq_head, so this loop becomes a busy-spin. Consider using submit_and_wait() or yielding the CPU in this path.


Design Issues

4. Inconsistent #[cfg] conditions across the feature

The uring_setup_sqpoll field on Builder is gated by #[cfg(all(tokio_unstable, feature = "io-uring"))], but the public method inside cfg_io_uring! additionally requires feature = "rt", feature = "fs", and target_os = "linux". The application sites add target_os = "linux" but not fs or rt.

Consequences:

  • Non-Linux with tokio_unstable + io-uring: field exists and compiles but can never be set (dead field)
  • io-uring without fs: field and application site compile but there is no public API to configure the value

These cfg guards should be aligned. Also consider whether requiring feature = "fs" is the right gate if SQPOLL would benefit future network io_uring operations.

5. Post-hoc UringContext configuration is fragile

setup_uring_sqpoll() sets sqpoll_idle on an already-created driver handle, relying on lazy io_uring initialization to ensure the value is present before the ring is created. This implicit ordering contract is easy to break accidentally. A cleaner design: add sqpoll_idle: Option<u32> to the Cfg struct in driver.rs, pass it through create_io_stack, and remove the post-Driver::new() setup block.


Documentation

6. uring_setup_sqpoll docstring is incomplete

The current docs only mention "can reduce syscall overhead." Please also document:

  • enable_io_uring() is required: SQPOLL has no effect if io_uring is not enabled
  • Linux only: this method is only available on Linux
  • Privilege requirement: kernels < 5.11 require CAP_SYS_ADMIN; if the caller lacks privilege, build() will return an error
  • idle parameter units: the value is in milliseconds (not currently stated)
  • CPU cost: the SQPOLL kernel thread consumes a full CPU core while active; most beneficial for sustained high-throughput I/O workloads

Minor

  • The two test functions are nearly identical; a shared helper would reduce duplication
  • The driver.rs refactor (passing &Cfg instead of individual fields) is a clean improvement with no issues

@martin-augment
Copy link
Copy Markdown
Owner Author

1758-1779: Documentation could mention privilege requirements and error behavior.

The SQPOLL feature typically requires elevated privileges (CAP_SYS_ADMIN or kernel 5.11+ with specific configs). Consider documenting:

  1. That SQPOLL may fail to initialize on systems without proper privileges
  2. What happens when initialization fails (falls back to regular io_uring or returns error?)

value:good-to-have; category:documentation; feedback: The CodeRabbit AI is correct! The documentation should be updated with any relevant information about the minimum Linux kernel version, any required privileges, etc. This will help the developers to decide whether they could use the SQPOLL functionality or not.

@martin-augment
Copy link
Copy Markdown
Owner Author

1. Tests may not exercise io_uring or SQPOLL at all

The tests call enable_all() but not enable_io_uring(). Since enable_io_uring() is a separate opt-in method, these tests may only exercise the non-io_uring fallback path and never actually test SQPOLL. Call enable_io_uring() explicitly in the tests.

value:annoying; category:bug; feedback: The Claude AI reviewer is not correct! enable_all() does call enable_io_uring() if the required Cargo features are enabled and tokio_unstable config is enabled too.

@martin-augment
Copy link
Copy Markdown
Owner Author

4. Inconsistent #[cfg] conditions across the feature

The uring_setup_sqpoll field on Builder is gated by #[cfg(all(tokio_unstable, feature = "io-uring"))], but the public method inside cfg_io_uring! additionally requires feature = "rt", feature = "fs", and target_os = "linux". The application sites add target_os = "linux" but not fs or rt.

Consequences:

  • Non-Linux with tokio_unstable + io-uring: field exists and compiles but can never be set (dead field)
  • io-uring without fs: field and application site compile but there is no public API to configure the value

These cfg guards should be aligned. Also consider whether requiring feature = "fs" is the right gate if SQPOLL would benefit future network io_uring operations.

value:useful; category:bug; feedback: The Claude AI reviewer is correct! All io_uring related functionalities should be gated behind the same cfg!. Otherwise some methods may be visible in some config but not usable due to others being missing in this config.

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