-
Notifications
You must be signed in to change notification settings - Fork 0
7960: feat: add SQPOLL support for io_uring #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,9 @@ pub struct Builder { | |
| enable_io: bool, | ||
| nevents: usize, | ||
|
|
||
| #[cfg(all(tokio_unstable, feature = "io-uring"))] | ||
| uring_setup_sqpoll: Option<u32>, | ||
|
|
||
| /// Whether or not to enable the time driver | ||
| enable_time: bool, | ||
|
|
||
|
|
@@ -275,6 +278,9 @@ impl Builder { | |
| enable_io: false, | ||
| nevents: 1024, | ||
|
|
||
| #[cfg(all(tokio_unstable, feature = "io-uring"))] | ||
| uring_setup_sqpoll: None, | ||
|
|
||
| // Time defaults to "off" | ||
| enable_time: false, | ||
|
|
||
|
|
@@ -1598,6 +1604,13 @@ impl Builder { | |
| cfg.timer_flavor = TimerFlavor::Traditional; | ||
| let (driver, driver_handle) = driver::Driver::new(cfg)?; | ||
|
|
||
| #[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); | ||
| } | ||
| } | ||
|
|
||
| // Blocking pool | ||
| let blocking_pool = blocking::create_blocking_pool(self, self.max_blocking_threads); | ||
| let blocking_spawner = blocking_pool.spawner().clone(); | ||
|
|
@@ -1742,6 +1755,28 @@ cfg_io_uring! { | |
| self.enable_io = true; | ||
| self | ||
| } | ||
|
|
||
| /// 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. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use tokio::runtime; | ||
| /// | ||
| /// let rt = runtime::Builder::new_multi_thread() | ||
| /// .enable_io_uring() | ||
| /// .uring_setup_sqpoll(2000) | ||
| /// .build() | ||
| /// .unwrap(); | ||
| /// ``` | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "io-uring")))] | ||
| pub fn uring_setup_sqpoll(&mut self, idle: u32) -> &mut Self { | ||
| self.uring_setup_sqpoll = Some(idle); | ||
| self | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1781,6 +1816,13 @@ cfg_rt_multi_thread! { | |
|
|
||
| let (driver, driver_handle) = driver::Driver::new(self.get_cfg())?; | ||
|
|
||
| #[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); | ||
| } | ||
| } | ||
|
Comment on lines
+1819
to
+1824
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This block of code is identical to the one in 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
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
|
||
| // Create the blocking pool | ||
| let blocking_pool = | ||
| blocking::create_blocking_pool(self, self.max_blocking_threads + worker_threads); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,13 +15,15 @@ const DEFAULT_RING_SIZE: u32 = 256; | |
| pub(crate) struct UringContext { | ||
| pub(crate) uring: Option<io_uring::IoUring>, | ||
| pub(crate) ops: slab::Slab<Lifecycle>, | ||
| pub(crate) sqpoll_idle: Option<u32>, | ||
| } | ||
|
|
||
| impl UringContext { | ||
| pub(crate) fn new() -> Self { | ||
| Self { | ||
| ops: Slab::new(), | ||
| uring: None, | ||
| sqpoll_idle: None, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -44,7 +46,11 @@ impl UringContext { | |
| return Ok(false); | ||
| } | ||
|
|
||
| let uring = IoUring::new(DEFAULT_RING_SIZE)?; | ||
| let uring = if let Some(idle) = self.sqpoll_idle { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Severity: medium 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| IoUring::builder().setup_sqpoll(idle).build(DEFAULT_RING_SIZE)? | ||
| } else { | ||
| IoUring::new(DEFAULT_RING_SIZE)? | ||
| }; | ||
|
|
||
| match uring.submitter().register_probe(probe) { | ||
| Ok(_) => {} | ||
|
|
@@ -97,6 +103,14 @@ impl UringContext { | |
| } | ||
|
|
||
| pub(crate) fn submit(&mut self) -> io::Result<()> { | ||
| if self.sqpoll_idle.is_some() { | ||
| let mut sq = self.ring_mut().submission(); | ||
| sq.sync(); | ||
| if !sq.need_wakeup() { | ||
| return Ok(()); | ||
| } | ||
| } | ||
|
|
||
| loop { | ||
| // Errors from io_uring_enter: https://man7.org/linux/man-pages/man2/io_uring_enter.2.html#ERRORS | ||
| match self.ring().submit() { | ||
|
|
@@ -164,6 +178,11 @@ impl Handle { | |
| &self.uring_context | ||
| } | ||
|
|
||
| pub(crate) fn setup_uring_sqpoll(&self, idle: u32) { | ||
| let mut guard = self.get_uring().lock(); | ||
| guard.sqpoll_idle = Some(idle); | ||
| } | ||
|
|
||
| /// Check if the io_uring context is initialized. If not, it will try to initialize it. | ||
| /// Then, check if the provided opcode is supported. | ||
| /// | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| #![cfg(all( | ||
| tokio_unstable, | ||
| feature = "io-uring", | ||
| feature = "rt", | ||
| feature = "fs", | ||
| target_os = "linux" | ||
| ))] | ||
|
|
||
| use std::io::{Read, Seek, SeekFrom}; | ||
| use tempfile::NamedTempFile; | ||
| use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; | ||
| use tokio::runtime::Builder; | ||
|
|
||
| #[test] | ||
| fn test_sqpoll_current_thread() { | ||
| let rt = Builder::new_current_thread() | ||
| .enable_all() | ||
| .uring_setup_sqpoll(1000) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| rt.block_on(async { | ||
| let mut temp = NamedTempFile::new().unwrap(); | ||
| let path = temp.path().to_path_buf(); | ||
|
|
||
| let mut file = tokio::fs::OpenOptions::new() | ||
| .read(true) | ||
| .write(true) | ||
| .open(&path) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| file.write_all(b"hello").await.unwrap(); | ||
| file.flush().await.unwrap(); | ||
|
|
||
| // Check if data was actually written to the underlying file | ||
| let mut buf = vec![0; 5]; | ||
| temp.as_file_mut().seek(SeekFrom::Start(0)).unwrap(); | ||
| temp.as_file_mut().read_exact(&mut buf).unwrap(); | ||
| assert_eq!(&buf, b"hello"); | ||
|
|
||
| file.seek(std::io::SeekFrom::Start(0)).await.unwrap(); | ||
| let mut buf = vec![0; 5]; | ||
| file.read_exact(&mut buf).await.unwrap(); | ||
| assert_eq!(&buf, b"hello"); | ||
| }); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_sqpoll_multi_thread() { | ||
| let rt = Builder::new_multi_thread() | ||
| .worker_threads(2) | ||
| .enable_all() | ||
| .uring_setup_sqpoll(1000) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| rt.block_on(async { | ||
| let mut temp = NamedTempFile::new().unwrap(); | ||
| let path = temp.path().to_path_buf(); | ||
|
|
||
| let mut file = tokio::fs::OpenOptions::new() | ||
| .read(true) | ||
| .write(true) | ||
| .open(&path) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| file.write_all(b"world").await.unwrap(); | ||
| file.flush().await.unwrap(); | ||
|
|
||
| // Check if data was actually written to the underlying file | ||
| let mut buf = vec![0; 5]; | ||
| temp.as_file_mut().seek(SeekFrom::Start(0)).unwrap(); | ||
| temp.as_file_mut().read_exact(&mut buf).unwrap(); | ||
| assert_eq!(&buf, b"world"); | ||
|
|
||
| file.seek(std::io::SeekFrom::Start(0)).await.unwrap(); | ||
| let mut buf = vec![0; 5]; | ||
| file.read_exact(&mut buf).await.unwrap(); | ||
| assert_eq!(&buf, b"world"); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
cfghere doesn’t match the conditions under which the io_uring pieces (includingsetup_uring_sqpoll) are actually compiled (cfg_io_uring!also requiresfeature = "rt"andfeature = "fs"). As written, building withtokio_unstable + io-uringon Linux but withoutfscan try to compile this call path and fail due to missing symbols.Severity: high
Other Locations
tokio/src/runtime/builder.rs:1819🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
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.