From de46479ee4c065eb214c37af9be75b07df72f284 Mon Sep 17 00:00:00 2001 From: chillfish8 Date: Sun, 1 Jun 2025 00:07:10 +0100 Subject: [PATCH 1/4] Add missing `register_files` APIs --- io-uring-test/src/main.rs | 2 + io-uring-test/src/tests/register.rs | 143 ++++++++++++++++++++++++++++ src/submit.rs | 64 ++++++++++++- 3 files changed, 207 insertions(+), 2 deletions(-) diff --git a/io-uring-test/src/main.rs b/io-uring-test/src/main.rs index f2282c5e..d6fb3b71 100644 --- a/io-uring-test/src/main.rs +++ b/io-uring-test/src/main.rs @@ -75,6 +75,8 @@ fn test( // register tests::register::test_register_files_sparse(&mut ring, &test)?; + tests::register::test_register_files_tags(&mut ring, &test)?; + tests::register::test_register_files_update_tag(&mut ring, &test)?; tests::register_buffers::test_register_buffers(&mut ring, &test)?; tests::register_buffers::test_register_buffers_update(&mut ring, &test)?; tests::register_buf_ring::test_register_buf_ring(&mut ring, &test)?; diff --git a/io-uring-test/src/tests/register.rs b/io-uring-test/src/tests/register.rs index f4d3efd5..33dd1f8e 100644 --- a/io-uring-test/src/tests/register.rs +++ b/io-uring-test/src/tests/register.rs @@ -1,5 +1,8 @@ +use std::os::fd::AsRawFd; +use anyhow::{bail, Context}; use crate::Test; use io_uring::{cqueue, opcode, squeue, IoUring}; +use io_uring::cqueue::Entry; pub fn test_register_files_sparse( ring: &mut IoUring, @@ -68,3 +71,143 @@ pub fn test_register_files_sparse( + ring: &mut IoUring, + test: &Test, +) -> anyhow::Result<()> { + // register_files_sparse was introduced in kernel 5.13, we need to check if resource tagging + // is available. + require!( + test; + ring.params().is_feature_resource_tagging(); + ); + + println!("test register_files_tags"); + + let file1 = tempfile::tempfile()?; + let file2 = tempfile::tempfile()?; + let file3 = tempfile::tempfile()?; + + let descriptors = &[ + file1.as_raw_fd(), + file2.as_raw_fd(), + file3.as_raw_fd(), + ]; + + let flags = &[0, 1, 2]; + + ring.submitter() + .register_files_tags(descriptors, flags) + .context("register_files_tags failed")?; + + // See that same call again, with any value, will fail because a direct table cannot be built + // over an existing one. + if let Ok(()) = ring.submitter().register_files_tags(descriptors, flags) { + bail!("register_files_tags should not have succeeded twice in a row"); + } + + // See that the direct table can be removed. + ring.submitter() + .unregister_files() + .context("unregister_files failed")?; + + // See that a second attempt to remove the direct table would fail. + if let Ok(()) = ring.submitter().unregister_files() { + bail!("should not have succeeded twice in a row"); + } + + // Check that we get completion events for unregistering files with non-zero tags. + let mut completion = ring.completion(); + completion.sync(); + + let cqes = completion + .map(Into::into) + .collect::>(); + + if cqes.len() != 2 { + bail!("incorrect number of completion events: {cqes:?}") + } + + for event in cqes { + if event.flags() != 0 || event.result() != 0 { + bail!( + "completion events from unregistering tagged \ + files should have zeroed flags and result fields" + ); + } + + let tag = event.user_data(); + if !(1..3).contains(&tag) { + bail!("completion event user data does not contain one of the possible tag values") + } + } + + Ok(()) +} + +pub fn test_register_files_update_tag( + ring: &mut IoUring, + test: &Test, +) -> anyhow::Result<()> { + // register_files_update_tag was introduced in kernel 5.13, we need to check if resource tagging + // is available. + require!( + test; + ring.params().is_feature_resource_tagging(); + ); + + println!("test register_files_update_tag"); + + let descriptors = &[ + -1, + -1, + -1, + ]; + let flags = &[0, 0, 0]; + ring.submitter() + .register_files_tags(descriptors, flags) + .context("register_files_tags failed")?; + + let file = tempfile::tempfile() + .context("create temp file")?; + + // Update slot `1` with a tagged file + ring.submitter() + .register_files_update_tag(1, &[file.as_raw_fd()], &[1]) + .context("register_files_update_tag failed")?; + + // Update slot `2` with an untagged file + ring.submitter() + .register_files_update_tag(1, &[file.as_raw_fd()], &[0]) + .context("register_files_update_tag failed")?; + + ring.submitter() + .unregister_files() + .context("unregister_files failed")?; + + // Check that we get completion events for unregistering files with non-zero tags. + let mut completion = ring.completion(); + completion.sync(); + + let cqes = completion + .map(Into::into) + .collect::>(); + + if cqes.len() != 1 { + bail!("incorrect number of completion events: {cqes:?}") + } + + if cqes[0].result() != 0 || cqes[0].flags() != 0 { + bail!( + "completion events from unregistering tagged \ + files should have zeroed flags and result fields" + ); + } + + if cqes[0].user_data() != 1 { + bail!("completion event user data does not contain tag of registered file"); + } + + Ok(()) +} diff --git a/src/submit.rs b/src/submit.rs index 70e6c942..e906b629 100644 --- a/src/submit.rs +++ b/src/submit.rs @@ -322,9 +322,13 @@ impl<'a> Submitter<'a> { /// /// Each fd may be -1, in which case it is considered "sparse", and can be filled in later with /// [`register_files_update`](Self::register_files_update). + /// + /// Note that before 5.13 registering buffers would wait for the ring to idle. + /// If the application currently has requests in-flight, the registration will + /// wait for those to finish before proceeding. /// - /// Note that this will wait for the ring to idle; it will only return once all active requests - /// are complete. Use [`register_files_update`](Self::register_files_update) to avoid this. + /// You can use [`register_files_update`](Self::register_files_update) to execute + /// this operation asynchronously. pub fn register_files(&self, fds: &[RawFd]) -> io::Result<()> { execute( self.fd.as_raw_fd(), @@ -335,6 +339,35 @@ impl<'a> Submitter<'a> { .map(drop) } + /// Variant of [`register_files`](Self::register_files) + /// with resource tagging. + /// + /// Each fd may be -1, in which case it is considered "sparse", and can be filled in later with + /// [`register_files_update`](Self::register_files_update). + /// + /// `tags` should be the same length as `fds` and contain the + /// tag value corresponding to the file descriptor at the same index. + /// + /// See [`register_buffers2`](Self::register_buffers2) + /// for more information about resource tagging. + /// + /// Available since Linux 5.13. + pub fn register_files_tags(&self, fds: &[RawFd], tags: &[u64]) -> io::Result<()> { + let rr = sys::io_uring_rsrc_register { + nr: fds.len().min(tags.len()) as _, + data: fds.as_ptr() as _, + tags: tags.as_ptr() as _, + ..Default::default() + }; + execute( + self.fd.as_raw_fd(), + sys::IORING_REGISTER_FILES2, + cast_ptr::(&rr).cast(), + mem::size_of::() as _, + ) + .map(drop) + } + /// This operation replaces existing files in the registered file set with new ones, /// either turning a sparse entry (one where fd is equal to -1) into a real one, removing an existing entry (new one is set to -1), /// or replacing an existing entry with a new existing entry. The `offset` parameter specifies @@ -357,6 +390,33 @@ impl<'a> Submitter<'a> { Ok(ret as _) } + /// Variant of [`register_files_update`](Self::register_files_update) + /// with resource tagging. + /// + /// `tags` should be the same length as `fds` and contain the + /// tag value corresponding to the file descriptor at the same index. + /// + /// See [`register_buffers2`](Self::register_buffers2) + /// for more information about resource tagging. + /// + /// Available since Linux 5.13. + pub fn register_files_update_tag(&self, offset: u32, fds: &[RawFd], tags: &[u64]) -> io::Result<()> { + let rr = sys::io_uring_rsrc_update2 { + offset, + nr: fds.len().min(tags.len()) as _, + data: fds.as_ptr() as _, + tags: tags.as_ptr() as _, + ..Default::default() + }; + execute( + self.fd.as_raw_fd(), + sys::IORING_REGISTER_FILES_UPDATE2, + cast_ptr::(&rr).cast(), + mem::size_of::() as _, + ) + .map(drop) + } + /// Register an eventfd created by [`eventfd`](libc::eventfd) with the io_uring instance. pub fn register_eventfd(&self, eventfd: RawFd) -> io::Result<()> { execute( From 34ae4d1ddfe1d96f0e79cd6396753c86292d98d4 Mon Sep 17 00:00:00 2001 From: chillfish8 Date: Sun, 1 Jun 2025 12:36:56 +0100 Subject: [PATCH 2/4] Fix formatting & tests on CI --- io-uring-test/src/tests/register.rs | 89 +++++++++++++++-------------- src/submit.rs | 19 +++--- 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/io-uring-test/src/tests/register.rs b/io-uring-test/src/tests/register.rs index 33dd1f8e..05470876 100644 --- a/io-uring-test/src/tests/register.rs +++ b/io-uring-test/src/tests/register.rs @@ -1,8 +1,8 @@ -use std::os::fd::AsRawFd; -use anyhow::{bail, Context}; use crate::Test; -use io_uring::{cqueue, opcode, squeue, IoUring}; +use anyhow::{bail, Context}; use io_uring::cqueue::Entry; +use io_uring::{cqueue, opcode, squeue, IoUring}; +use std::os::fd::AsRawFd; pub fn test_register_files_sparse( ring: &mut IoUring, @@ -85,50 +85,52 @@ pub fn test_register_files_tags( println!("test register_files_tags"); + let (submitter, _, mut completion) = ring.split(); + let file1 = tempfile::tempfile()?; let file2 = tempfile::tempfile()?; let file3 = tempfile::tempfile()?; - let descriptors = &[ - file1.as_raw_fd(), - file2.as_raw_fd(), - file3.as_raw_fd(), - ]; + let descriptors = &[file1.as_raw_fd(), file2.as_raw_fd(), file3.as_raw_fd()]; let flags = &[0, 1, 2]; - ring.submitter() + submitter .register_files_tags(descriptors, flags) .context("register_files_tags failed")?; // See that same call again, with any value, will fail because a direct table cannot be built // over an existing one. - if let Ok(()) = ring.submitter().register_files_tags(descriptors, flags) { + if let Ok(()) = submitter.register_files_tags(descriptors, flags) { bail!("register_files_tags should not have succeeded twice in a row"); } // See that the direct table can be removed. - ring.submitter() + submitter .unregister_files() .context("unregister_files failed")?; // See that a second attempt to remove the direct table would fail. - if let Ok(()) = ring.submitter().unregister_files() { + if let Ok(()) = submitter.unregister_files() { bail!("should not have succeeded twice in a row"); } - + // Check that we get completion events for unregistering files with non-zero tags. - let mut completion = ring.completion(); completion.sync(); - - let cqes = completion - .map(Into::into) - .collect::>(); - + + if completion.is_empty() { + submitter + .submit_and_wait(1) + .context("waiting for ring completions failed")?; + completion.sync(); + } + + let cqes = completion.map(Into::into).collect::>(); + if cqes.len() != 2 { bail!("incorrect number of completion events: {cqes:?}") } - + for event in cqes { if event.flags() != 0 || event.result() != 0 { bail!( @@ -156,48 +158,49 @@ pub fn test_register_files_update_tag>(); + + if completion.is_empty() { + submitter + .submit_and_wait(1) + .context("waiting for ring completions failed")?; + completion.sync(); + } + + let cqes = completion.map(Into::into).collect::>(); if cqes.len() != 1 { bail!("incorrect number of completion events: {cqes:?}") } - + if cqes[0].result() != 0 || cqes[0].flags() != 0 { bail!( "completion events from unregistering tagged \ @@ -208,6 +211,6 @@ pub fn test_register_files_update_tag Submitter<'a> { /// /// Each fd may be -1, in which case it is considered "sparse", and can be filled in later with /// [`register_files_update`](Self::register_files_update). - /// + /// /// Note that before 5.13 registering buffers would wait for the ring to idle. - /// If the application currently has requests in-flight, the registration will + /// If the application currently has requests in-flight, the registration will /// wait for those to finish before proceeding. /// - /// You can use [`register_files_update`](Self::register_files_update) to execute + /// You can use [`register_files_update`](Self::register_files_update) to execute /// this operation asynchronously. pub fn register_files(&self, fds: &[RawFd]) -> io::Result<()> { execute( @@ -344,7 +344,7 @@ impl<'a> Submitter<'a> { /// /// Each fd may be -1, in which case it is considered "sparse", and can be filled in later with /// [`register_files_update`](Self::register_files_update). - /// + /// /// `tags` should be the same length as `fds` and contain the /// tag value corresponding to the file descriptor at the same index. /// @@ -367,7 +367,7 @@ impl<'a> Submitter<'a> { ) .map(drop) } - + /// This operation replaces existing files in the registered file set with new ones, /// either turning a sparse entry (one where fd is equal to -1) into a real one, removing an existing entry (new one is set to -1), /// or replacing an existing entry with a new existing entry. The `offset` parameter specifies @@ -400,7 +400,12 @@ impl<'a> Submitter<'a> { /// for more information about resource tagging. /// /// Available since Linux 5.13. - pub fn register_files_update_tag(&self, offset: u32, fds: &[RawFd], tags: &[u64]) -> io::Result<()> { + pub fn register_files_update_tag( + &self, + offset: u32, + fds: &[RawFd], + tags: &[u64], + ) -> io::Result<()> { let rr = sys::io_uring_rsrc_update2 { offset, nr: fds.len().min(tags.len()) as _, @@ -416,7 +421,7 @@ impl<'a> Submitter<'a> { ) .map(drop) } - + /// Register an eventfd created by [`eventfd`](libc::eventfd) with the io_uring instance. pub fn register_eventfd(&self, eventfd: RawFd) -> io::Result<()> { execute( From 7b1792c4c69c5f5caa55fd58e62e948d270fa9f3 Mon Sep 17 00:00:00 2001 From: chillfish8 Date: Sun, 1 Jun 2025 13:54:43 +0100 Subject: [PATCH 3/4] use eventfd for reliable completion notify in tests --- io-uring-test/src/main.rs | 12 ++++++++++++ io-uring-test/src/tests/register.rs | 26 ++++++++++++-------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/io-uring-test/src/main.rs b/io-uring-test/src/main.rs index d6fb3b71..a1c5b48e 100644 --- a/io-uring-test/src/main.rs +++ b/io-uring-test/src/main.rs @@ -2,6 +2,7 @@ mod utils; mod tests; +use anyhow::Context; use io_uring::{cqueue, squeue, IoUring, Probe}; use std::cell::Cell; @@ -9,6 +10,7 @@ pub struct Test { probe: Probe, target: Option, count: Cell, + event_fd: libc::c_int, } fn main() -> anyhow::Result<()> { @@ -59,8 +61,18 @@ fn test( println!("probe: {:?}", probe); println!(); + // Used for waiting on events, this is more reliable for CI & general use cases + // than doing a full event loop. + // Since this is just for testing, we don't really need to close the event fd explicitly, + // it'll just be cleaned up at the end. + let event_fd = unsafe { libc::eventfd(0, 0) }; + ring.submitter() + .register_eventfd(event_fd) + .context("register event fd")?; + let test = Test { probe, + event_fd, target: std::env::args().nth(1), count: Cell::new(0), }; diff --git a/io-uring-test/src/tests/register.rs b/io-uring-test/src/tests/register.rs index 05470876..836fd1cc 100644 --- a/io-uring-test/src/tests/register.rs +++ b/io-uring-test/src/tests/register.rs @@ -115,16 +115,15 @@ pub fn test_register_files_tags( bail!("should not have succeeded twice in a row"); } + // Wait for the event fd to complete indicating we have completions. + unsafe { + let mut value: u64 = 0; + let _ = libc::eventfd_read(test.event_fd, (&mut value) as *mut _); + } + // Check that we get completion events for unregistering files with non-zero tags. completion.sync(); - if completion.is_empty() { - submitter - .submit_and_wait(1) - .context("waiting for ring completions failed")?; - completion.sync(); - } - let cqes = completion.map(Into::into).collect::>(); if cqes.len() != 2 { @@ -185,16 +184,15 @@ pub fn test_register_files_update_tag>(); if cqes.len() != 1 { From 6e7b8b1915e6be0138b22e4a286246c72795b8a2 Mon Sep 17 00:00:00 2001 From: chillfish8 Date: Sun, 1 Jun 2025 18:50:52 +0100 Subject: [PATCH 4/4] reset the eventfd counter before running --- io-uring-test/src/main.rs | 13 +++++++++++++ io-uring-test/src/tests/register.rs | 2 ++ 2 files changed, 15 insertions(+) diff --git a/io-uring-test/src/main.rs b/io-uring-test/src/main.rs index a1c5b48e..1a50efed 100644 --- a/io-uring-test/src/main.rs +++ b/io-uring-test/src/main.rs @@ -13,6 +13,19 @@ pub struct Test { event_fd: libc::c_int, } +impl Test { + /// Reset the eventfd counter, this can be used to prevent + /// previous operations affecting the test. + fn reset_eventfd_counter(&self) { + // Reset the event fd state without blocking. + unsafe { + let _ = libc::eventfd_write(self.event_fd, 1); + let mut val: u64 = 0; + let _ = libc::eventfd_read(self.event_fd, (&mut val) as *mut _); + }; + } +} + fn main() -> anyhow::Result<()> { let entries = 8; diff --git a/io-uring-test/src/tests/register.rs b/io-uring-test/src/tests/register.rs index 836fd1cc..fc311b6c 100644 --- a/io-uring-test/src/tests/register.rs +++ b/io-uring-test/src/tests/register.rs @@ -84,6 +84,7 @@ pub fn test_register_files_tags( ); println!("test register_files_tags"); + test.reset_eventfd_counter(); let (submitter, _, mut completion) = ring.split(); @@ -161,6 +162,7 @@ pub fn test_register_files_update_tag