Skip to content

7791: fs: Implement io-uring::Op<Statx> and use it to implement fs::try_exists and complete the todo's in read_uring#39

Open
martin-augment wants to merge 3 commits intomasterfrom
pr-7791-2025-12-29-05-26-01
Open

7791: fs: Implement io-uring::Op<Statx> and use it to implement fs::try_exists and complete the todo's in read_uring#39
martin-augment wants to merge 3 commits intomasterfrom
pr-7791-2025-12-29-05-26-01

Conversation

@martin-augment
Copy link
Owner

7791: To review by AI

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Walkthrough

The PR introduces comprehensive changes to Tokio's io-uring integration for file operations. It adds linux-raw-sys as a dependency, introduces a new statx module for metadata retrieval via io-uring, refactors file opening to work with OwnedFd instead of File objects directly, and updates filesystem operations (read, write, try_exists) to leverage the new paths. Test utilities are enhanced with bidirectional OwnedFd/MockFile conversions, and lower-level utilities are added for managing uninitialized boxed values.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr-7791-2025-12-29-05-26-01

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.

@claude
Copy link

claude bot commented Dec 29, 2025

Pull Request Review: Implement io-uring Op

Overview

This PR implements statx support for io-uring operations and uses it to complete TODOs in read_uring and implement fs::try_exists. The implementation follows established patterns in the codebase and makes good progress toward fully utilizing io-uring operations.


Code Quality & Best Practices

Strengths:

  • Follows existing io-uring operation patterns well (similar structure to Open, Read, Write)
  • Good use of the Completable and Cancellable traits
  • Proper SAFETY comments for unsafe operations
  • Consistent naming conventions throughout

Issues & Concerns:

  1. Incomplete doc comment (tokio/src/io/uring/statx.rs:56)

    /// Submit a request to open a file.
    

    This should say "Submit a request to get file metadata" or similar, not "open a file" (copy-paste error).

  2. Commented-out code (tokio/src/io/uring/statx.rs:81-83)

    // pub(crate) fn symlink_metadata(path: &Path) -> io::Result<Op<Statx>> {
    //     Op::statx(path, false)
    // }

    Unless there's a specific reason to keep this for future work, consider removing it. If it's intentional, add a TODO comment explaining why.

  3. MSRV compatibility workarounds
    The box_new_uninit and box_assume_init helper functions are properly marked with TODO comments for MSRV 1.82. This is good practice.


Potential Bugs & Issues

Critical:

  1. Integer overflow handling (tokio/src/fs/read_uring.rs:24-26)

    let size_hint: Option<usize> = size_hint
        .ok()
        .map(|m| usize::try_from(m.len()).unwrap_or(usize::MAX));

    While unwrap_or(usize::MAX) prevents panics, using usize::MAX as a fallback could cause issues. On 32-bit systems, a file larger than 4GB would be capped at usize::MAX, which might lead to incorrect buffer pre-allocation. Consider whether this is the intended behavior or if an error should be returned instead.

  2. statx availability (tokio/src/io/uring/statx.rs)
    The statx syscall requires Linux kernel 4.11+, while io-uring requires 5.1+. Since io-uring is already the baseline, this should be fine, but it might be worth documenting that statx is always available when io-uring is available.

Minor:

  1. Error handling consistency (tokio/src/fs/try_exists.rs:21)
    The existing test at line 21 excludes io-uring from the permission test:
    #[cfg(all(
        unix,
        not(any(target_os = "freebsd", all(tokio_unstable, feature = "io-uring")))
    ))]
    This suggests that statx via io-uring might behave differently for permission errors. This exclusion should be investigated or documented.

Performance Considerations

Positive:

  • Eliminates blocking metadata call in read_uring, making it fully async with io-uring
  • Avoids unnecessary file descriptor conversion in the read path
  • try_exists now uses a single statx syscall instead of opening/closing a file

Observations:

  • The refactoring in open_options.rs changes from cloning early to passing by reference, which is more efficient
  • The change to return OwnedFd from Op::open is cleaner and avoids intermediate conversions

Security Concerns

No major security issues identified.

Notes:

  • The AT_EMPTY_PATH flag usage (tokio/src/io/uring/statx.rs:119) is correct for statx on a file descriptor
  • Proper use of O_CLOEXEC is maintained in the open operations
  • Path handling via cstr() properly validates paths (no null bytes)

Test Coverage

Current state:

  • Existing test fs_try_exists.rs covers the try_exists functionality
  • Existing test fs_uring_read.rs covers the read functionality
  • Tests verify cancellation behavior for io-uring operations

Gaps:

  1. No direct tests for statx operations - Consider adding tests that specifically verify:

    • Op::metadata() returns correct file size
    • Op::metadata_fd() returns correct file size from a file descriptor
    • Error handling when statx fails (e.g., file not found, permission denied)
  2. Edge cases not covered:

    • Very large files (> 4GB) on 32-bit systems with the usize::MAX fallback
    • Symlink handling in try_exists (though the existing test may cover this)

Additional Recommendations

  1. Consider extracting common statx flags:

    const STATX_SYNC_FLAGS: u32 = linux_raw_sys::general::AT_STATX_SYNC_AS_STAT;

    This would make the code more maintainable.

  2. Add integration test for the complete read_uring path to verify the metadata optimization actually works end-to-end.

  3. Documentation: Consider adding a module-level doc comment to statx.rs explaining the purpose and kernel requirements.


Summary

This is a solid implementation that follows Tokio's established patterns well. The main concerns are:

  1. Fix the incorrect doc comment
  2. Investigate/document the test exclusion for io-uring permission tests
  3. Consider the usize::MAX fallback behavior for large files
  4. Add dedicated tests for the new statx functionality

The code is production-ready after addressing the doc comment issue, with the other items being nice-to-haves for robustness.

Recommendation: Approve with minor changes requested.

Copy link

@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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tokio/src/io/uring/utils.rs (1)

15-19: Consider adding safety documentation to box_assume_init.

The implementation of box_assume_init is correct, but the function would benefit from explicit safety documentation explaining the caller's responsibility to ensure the value is properly initialized before calling this function.

🔎 Suggested safety documentation
+/// # Safety
+///
+/// The caller must ensure that `boxed` contains a fully initialized value of type `T`.
+/// Calling this function on a `Box<MaybeUninit<T>>` that has not been properly initialized
+/// results in undefined behavior.
 // TODO(MSRV 1.82): When bumping MSRV, switch to `Box::<MaybeUninit<T>>::assume_init()`.
 pub(crate) unsafe fn box_assume_init<T>(boxed: Box<MaybeUninit<T>>) -> Box<T> {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a68edc4 and 972c7c1.

📒 Files selected for processing (13)
  • tokio/Cargo.toml
  • tokio/src/fs/mocks.rs
  • tokio/src/fs/open_options.rs
  • tokio/src/fs/open_options/uring_open_options.rs
  • tokio/src/fs/read.rs
  • tokio/src/fs/read_uring.rs
  • tokio/src/fs/try_exists.rs
  • tokio/src/fs/write.rs
  • tokio/src/io/uring/mod.rs
  • tokio/src/io/uring/open.rs
  • tokio/src/io/uring/statx.rs
  • tokio/src/io/uring/utils.rs
  • tokio/src/runtime/driver/op.rs
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-28T15:30:50.368Z
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 9
File: tokio/src/fs/file.rs:824-855
Timestamp: 2025-10-28T15:30:50.368Z
Learning: In io-uring write implementations for tokio::fs::File, writes must loop until the buffer is fully drained. A single Op::write_at call may perform a partial write. The code must: (1) loop calling Op::write_at until buf.is_empty(), (2) return io::ErrorKind::WriteZero when 0 bytes are written, (3) only return Operation::Write(Ok(())) after the buffer is completely empty. This applies to both poll_write and poll_write_vectored implementations.

Applied to files:

  • tokio/src/fs/write.rs
  • tokio/src/fs/read_uring.rs
  • tokio/src/runtime/driver/op.rs
📚 Learning: 2025-10-28T15:30:54.516Z
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 9
File: tokio/src/fs/file.rs:970-981
Timestamp: 2025-10-28T15:30:54.516Z
Learning: In tokio fs file io-uring write operations (both vectored and non-vectored), the write must loop until the buffer is fully drained. Operation::Write(Ok()) should only be returned when buf.is_empty() is true. If 0 bytes are written, return Operation::Write(Err(io::ErrorKind::WriteZero.into())). Otherwise, continue executing new writes until the buffer is empty or an error occurs.

Applied to files:

  • tokio/src/fs/write.rs
  • tokio/src/runtime/driver/op.rs
📚 Learning: 2025-10-30T14:46:01.443Z
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 10
File: tokio-util/src/io/mod.rs:17-17
Timestamp: 2025-10-30T14:46:01.443Z
Learning: In tokio-util, the entire `io` module at `tokio-util/src/lib.rs` is already gated with `cfg_io!` macro, which applies `#[cfg(feature = "io")]` to all contents. Submodules declared within `tokio-util/src/io/mod.rs` inherit this feature gate and don't need additional `cfg_io!` wrapping unless they have specific requirements. The `cfg_io_util!` macro in the same file is for items that specifically require the `io-util` feature, not the general `io` feature.

Applied to files:

  • tokio/src/fs/write.rs
  • tokio/src/fs/open_options.rs
  • tokio/Cargo.toml
📚 Learning: 2025-11-05T14:24:03.551Z
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 13
File: tokio-util/src/io/simplex.rs:184-209
Timestamp: 2025-11-05T14:24:03.551Z
Learning: In AsyncWrite implementations, zero-length writes (when buf.is_empty()) should return Poll::Ready(Ok(0)) immediately without blocking or registering wakers. Only non-empty buffers that cannot be written due to backpressure should return Poll::Pending. This prevents indefinite hangs when empty slices are written.

Applied to files:

  • tokio/src/runtime/driver/op.rs
🧬 Code graph analysis (11)
tokio/src/io/uring/mod.rs (1)
tokio/src/io/uring/statx.rs (1)
  • statx (57-75)
tokio/src/fs/mocks.rs (1)
tokio/src/fs/file.rs (2)
  • from (888-890)
  • from_raw_fd (919-923)
tokio/src/fs/open_options/uring_open_options.rs (2)
tokio/src/fs/open_options.rs (1)
  • open (519-521)
tokio/src/io/uring/open.rs (1)
  • open (40-58)
tokio/src/io/uring/open.rs (3)
tokio/src/io/uring/statx.rs (2)
  • complete (39-42)
  • complete (95-101)
tokio/src/runtime/driver/op.rs (1)
  • complete (118-118)
tokio/src/io/uring/write.rs (1)
  • complete (16-18)
tokio/src/fs/try_exists.rs (2)
tokio/src/runtime/handle.rs (1)
  • current (144-148)
tokio/src/io/uring/statx.rs (1)
  • metadata (77-79)
tokio/src/io/uring/utils.rs (1)
tokio/src/io/uring/statx.rs (2)
  • box_new_uninit (59-59)
  • box_new_uninit (116-116)
tokio/src/fs/read_uring.rs (3)
tokio/src/fs/open_options/uring_open_options.rs (1)
  • new (24-35)
tokio/src/runtime/driver/op.rs (1)
  • new (67-74)
tokio/src/io/uring/statx.rs (1)
  • metadata_fd (115-135)
tokio/src/fs/open_options.rs (3)
tokio/src/fs/file.rs (2)
  • from_std (277-287)
  • from (888-890)
tokio/src/fs/open_options/uring_open_options.rs (1)
  • from (120-134)
tokio/src/fs/mod.rs (1)
  • asyncify (312-324)
tokio/src/fs/read.rs (1)
tokio/src/fs/read_uring.rs (1)
  • read_uring (19-35)
tokio/src/io/uring/statx.rs (3)
tokio/src/io/uring/utils.rs (3)
  • box_assume_init (16-19)
  • box_new_uninit (10-13)
  • cstr (5-7)
tokio/src/io/uring/open.rs (3)
  • complete (22-25)
  • complete_with_error (27-29)
  • cancel (33-35)
tokio/src/runtime/driver/op.rs (5)
  • complete (118-118)
  • complete_with_error (124-124)
  • cancel (129-129)
  • from (104-112)
  • new (67-74)
tokio/src/runtime/driver/op.rs (1)
tokio/src/io/uring/statx.rs (1)
  • statx (57-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: claude-review
🔇 Additional comments (19)
tokio/src/io/uring/mod.rs (1)

3-3: LGTM! Module addition looks correct.

The new statx module is properly declared as pub(crate), making it available for internal use within the io-uring subsystem.

tokio/src/runtime/driver/op.rs (1)

6-6: LGTM! Cancellation support properly extended.

The addition of Statx and StatxFd variants to the CancelData enum correctly extends cancellation support to the new statx operations, following the established pattern for other io-uring operation types.

Also applies to: 23-24

tokio/src/fs/open_options/uring_open_options.rs (1)

114-116: LGTM! Clean async interface for io-uring file opening.

The new open() method provides a clean async interface that delegates to the io-uring operation and returns an OwnedFd, maintaining clear ownership semantics.

tokio/src/fs/mocks.rs (1)

109-116: LGTM! Test-only conversion properly implemented.

The From<OwnedFd> implementation correctly transfers ownership of the file descriptor from OwnedFd to MockFile using IntoRawFd and FromRawFd. This complements the existing reverse conversion and is properly scoped to test code only.

tokio/src/io/uring/utils.rs (1)

10-13: LGTM! Clean implementation with MSRV tracking.

The box_new_uninit helper correctly creates an uninitialized boxed value and includes a TODO comment for migrating to the standard library method when the MSRV is raised.

tokio/src/fs/read.rs (1)

57-57: LGTM! Improved path handling and code organization.

The refactoring improves efficiency by deferring the PathBuf allocation until it's actually needed (in the blocking path). The extraction of read_spawn_blocking also improves code modularity and separation of concerns.

Also applies to: 72-72, 76-82

tokio/src/io/uring/open.rs (1)

21-25: OwnedFd improves ownership semantics across io-uring integration.

The change from returning io::Result<crate::fs::File> to io::Result<OwnedFd> provides clearer ownership semantics and aligns with the broader refactoring across the io-uring integration. The implementation correctly uses OwnedFd::from_raw_fd to take ownership of the file descriptor. All call sites (read_uring.rs and write.rs) properly handle the new return type, passing OwnedFd to the corresponding io-uring operations.

tokio/Cargo.toml (1)

88-88: Version 0.12.1 is the latest stable release and has no known security vulnerabilities.

Verification confirms that linux-raw-sys 0.12.1 is the current stable version (as of December 23, 2025) with no public CVEs or security advisories. The dependency is properly gated behind the unstable tokio_unstable feature and the Linux-only target configuration, ensuring it only compiles when explicitly enabled.

tokio/src/fs/try_exists.rs (3)

25-44: LGTM! Clean io-uring path selection with efficient path handling.

The implementation correctly:

  • Borrows the path reference upfront, avoiding unnecessary cloning for the io-uring path
  • Only clones to PathBuf in the blocking fallback where ownership is required
  • Properly checks driver readiness before using the io-uring path

46-56: LGTM! Correct error mapping for existence check.

The io-uring path properly maps NotFound to Ok(false) while propagating other errors, matching the semantics of std::path::Path::try_exists.


58-61: LGTM!

The blocking fallback correctly clones the path to satisfy the 'static bound required by asyncify.

tokio/src/fs/read_uring.rs (2)

19-35: LGTM! Clean integration with UringOpenOptions and metadata_fd.

The flow correctly:

  • Opens the file with io-uring via UringOpenOptions
  • Retrieves metadata using Op::metadata_fd which returns both the result and the fd
  • Safely handles u64 to usize conversion overflow by defaulting to usize::MAX

107-132: LGTM! Proper EINTR retry handling.

The op_read function correctly loops on ErrorKind::Interrupted, which is the expected behavior for io-uring operations that may be interrupted by signals.

tokio/src/fs/open_options.rs (1)

519-558: LGTM! Clean separation between io-uring and standard paths.

The implementation:

  • Properly delegates to open_inner for path handling
  • Correctly checks driver readiness before using io-uring
  • Falls back to the standard path when io-uring is not available
  • Uses test-mockable StdFile for testability
tokio/src/fs/write.rs (1)

48-80: LGTM! Correctly implements partial write handling for io-uring.

The implementation properly:

  • Loops until the entire buffer is written (while buf_offset < total)
  • Returns WriteZero error when Ok(0) is received (per retrieved learnings)
  • Continues on Interrupted errors
  • Tracks both buffer and file offsets for correct positioning

Based on learnings, this matches the required pattern for io-uring write operations.

tokio/src/io/uring/statx.rs (4)

11-25: LGTM! Clean Metadata wrapper.

The Metadata struct properly wraps the raw statx structure and provides a safe accessor for the file size via stx_size.


36-53: LGTM! Correct Completable and Cancellable implementations.

The complete method correctly uses box_assume_init after the kernel has written to the buffer. The safety invariant is maintained because the kernel completes the statx operation before complete is called.


55-84: LGTM! Well-structured statx operation setup.

The implementation:

  • Correctly converts the path to CString and stores it to keep it alive during the operation
  • Uses AT_FDCWD for path-relative operations
  • Properly applies AT_SYMLINK_NOFOLLOW flag based on follow_symlinks parameter
  • Documents the commented-out symlink_metadata for future use

86-136: LGTM! StatxFd correctly returns both metadata and fd.

The StatxFd variant:

  • Preserves the OwnedFd through the completion, returning it as part of the tuple
  • Uses AT_EMPTY_PATH flag correctly with an empty C string literal (c"")
  • Properly documents the io-uring/statx man page reference for the empty path usage

@augmentcode
Copy link

augmentcode bot commented Dec 29, 2025

🤖 Augment PR Summary

Summary: Adds a new io-uring-backed Statx operation and uses it to improve/complete several filesystem helpers when io-uring is enabled.

Changes:

  • Introduce tokio::io::uring::statx implementing Op<Statx> / Op<StatxFd> using linux-raw-sys’s statx struct.
  • Switch io-uring Open completion to return OwnedFd, and adapt OpenOptions/UringOpenOptions to build tokio::fs::File from it.
  • Use StatxFd in read_uring to fetch a size hint without leaving TODOs, while keeping the fd for subsequent reads.
  • Implement io-uring fast-path for fs::try_exists via io-uring metadata lookup, with spawn-blocking fallback.
  • Refactor fs::read/fs::write codepaths to share spawn-blocking fallbacks and reduce unnecessary path cloning.

Technical Notes:

  • Adds an optional linux-raw-sys dependency behind the io-uring feature on Linux.
  • Extends cancellation bookkeeping with CancelData::Statx/StatxFd.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@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.

// https://man7.org/linux/man-pages/man2/statx.2.html
let statx_op = opcode::Statx::new(
types::Fd(fd.as_raw_fd()),
c"".as_ptr(),
Copy link

Choose a reason for hiding this comment

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

c"" C-string literals require a newer Rust version than this crate’s MSRV (1.71), so this will break builds that respect rust-version.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

Copy link
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! The build will fail with Rust .171 because c-string literals are stable since version 1.77. This should be reworked to not use c-string literals.

/// Submit a request to open a file.
fn statx(path: &Path, follow_symlinks: bool) -> io::Result<Op<Statx>> {
let path = cstr(path)?;
let mut buffer = box_new_uninit::<statx>();
Copy link

Choose a reason for hiding this comment

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

This allocates the output statx buffer as MaybeUninit and later reads the whole struct via assume_init; if the kernel ever leaves padding/unused fields uninitialized, that becomes UB. Consider ensuring the buffer is fully initialized (e.g., zeroed) before passing it to the kernel.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

Copy link
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! A check should be added that the returned buffer by the kernel is fully initialized or alternatively the buffer should be pre-initialized before being passed to the kernel. Prevents passing a corrected data to the user application in case of a bug in the kernel.

@martin-augment
Copy link
Owner Author

  1. Incomplete doc comment (tokio/src/io/uring/statx.rs:56)
    /// Submit a request to open a file.
    
    This should say "Submit a request to get file metadata" or similar, not "open a file" (copy-paste error).

value:good-to-have; category:documentation; feedback:The Claude AI reviewer is correct! The docstring is copy/pasted from somewhere else. The statx module is about reading the file's metadata, so the docstring should be fixed. Prevents releasing wrong documentation for the new method.

@martin-augment
Copy link
Owner Author

2. Commented-out code (tokio/src/io/uring/statx.rs:81-83)
rust // pub(crate) fn symlink_metadata(path: &Path) -> io::Result<Op<Statx>> { // Op::statx(path, false) // }
Unless there's a specific reason to keep this for future work, consider removing it. If it's intentional, add a TODO comment explaining why.

value:good-to-have; category:bug; feedback:The Claude AI reviewer is correct! There is no point in adding commented out code in a new module. It should be removed and an issue should be filled in the issue tracker for implementing it when the prerequisites are available. Prevents adding technical dept without explanation of the reason.

@martin-augment
Copy link
Owner Author

  • Integer overflow handling (tokio/src/fs/read_uring.rs:24-26)
    let size_hint: Option<usize> = size_hint
        .ok()
        .map(|m| usize::try_from(m.len()).unwrap_or(usize::MAX));
    While unwrap_or(usize::MAX) prevents panics, using usize::MAX as a fallback could cause issues. On 32-bit systems, a file larger than 4GB would be capped at usize::MAX, which might lead to incorrect buffer pre-allocation. Consider whether this is the intended behavior or if an error should be returned instead.

value:useful; category:bug; feedback:The Claude AI reviewer is correct! There is no problem with truncation here since this is just a hint that is used to decide how many bytes to pre-allocate for the buffer capacity. 0 should be used instead of usize::MAX as a default size when the actual size cannot be read. This way the buffer will be resized several times until the actual bytes are written but it won't pre-allocate more bytes than needed.

@martin-augment
Copy link
Owner Author

3. Error handling consistency (tokio/src/fs/try_exists.rs:21)
The existing test at line 21 excludes io-uring from the permission test:
rust #[cfg(all( unix, not(any(target_os = "freebsd", all(tokio_unstable, feature = "io-uring"))) ))]
This suggests that statx via io-uring might behave differently for permission errors. This exclusion should be investigated or documented.

value:annoying; category:bug; feedback:The Claude AI reviewer is not correct! There is no such code at try_exists.rs:21 at all! The AI model is hallucinating!

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