Skip to content

7799: implement unlink io-uring call to implement remove_file and remove_dir#43

Open
martin-augment wants to merge 1 commit intomasterfrom
pr-7799-2025-12-29-13-45-41
Open

7799: implement unlink io-uring call to implement remove_file and remove_dir#43
martin-augment wants to merge 1 commit intomasterfrom
pr-7799-2025-12-29-13-45-41

Conversation

@martin-augment
Copy link
Owner

7799: To review by AI

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Walkthrough

This pull request extends tokio's filesystem operations with io-uring support for file and directory removal. The changes add a new unlink module for io-uring integration that implements file and directory removal operations. The remove_file and remove_dir functions are refactored to conditionally route through io-uring when available, with fallback to blocking paths. Internal helper functions remove_file_uring, remove_dir_uring, remove_file_blocking, and remove_dir_blocking are introduced to manage these execution paths. The cancellation data structure is updated, replacing the Open variant with CString in the CancelData enum. Public API signatures remain unchanged.

✨ 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-7799-2025-12-29-13-45-41

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

Code Review: io-uring unlink implementation

Thank you for implementing io-uring support for remove_file and remove_dir operations! This is a valuable addition that can improve performance for these filesystem operations. Here's my detailed review:


Strengths

  1. Consistent Architecture: The implementation follows the established patterns from other io-uring operations (like open and read), maintaining consistency across the codebase.

  2. Proper Safety Documentation: Good use of #[allow(dead_code)] and safety comments explaining why the path field must remain valid for the operation's duration.

  3. Correct CancelData Refactor: The change from CancelData::Open(self) to CancelData::CString(self.path) in open.rs:34 is good - it properly unifies the cancellation handling and reduces enum variants.

  4. Proper Fallback Strategy: The implementation correctly falls back to the blocking implementation when io-uring is not available or not initialized.


🔍 Issues & Recommendations

1. Missing Test Coverage (High Priority)

The PR does not include any tests for the new io-uring unlink functionality. Looking at tokio/tests/fs_uring.rs, there's a clear pattern for testing io-uring features.

Recommendation: Add tests similar to the existing open_many_files and cancel_op_future tests:

  • Test remove_file with io-uring enabled
  • Test remove_dir with io-uring enabled
  • Test cancellation behavior for unlink operations
  • Test the fallback path when io-uring is unavailable

Example test structure:

#[test]
fn remove_file_with_uring() {
    fn run(rt: Runtime) {
        let (_tmp_files, paths) = create_tmp_files(10);
        rt.block_on(async move {
            for path in paths {
                tokio::fs::remove_file(&path).await.unwrap();
                assert!(!tokio::fs::try_exists(&path).await.unwrap());
            }
        });
    }
    
    for rt in rt_combinations() {
        run(rt());
    }
}

2. Incomplete Documentation (Medium Priority)

In tokio/src/io/uring/unlink.rs:35: The doc comment says "Submit a request to open a file" but it should say "Submit a request to unlink a file/directory".

-    /// Submit a request to open a file.
+    /// Submit a request to unlink a file or directory.

3. Error Handling Consideration (Low Priority)

The complete implementation in unlink.rs:20 uses cqe.result.map(drop) which is fine, but it might be worth verifying that all relevant error codes from unlinkat syscall are properly handled by the io-uring error conversion in CqeResult::from.

Common errors to consider:

  • ENOENT (file doesn't exist)
  • EISDIR / ENOTDIR (wrong type)
  • EACCES / EPERM (permission denied)
  • EBUSY (resource busy)

The current implementation should handle these correctly via io::Error::from_raw_os_error(-res) in op.rs:106, so this is more of a verification item.

4. Code Consistency: CancelData Pattern (Low Priority)

The CancelData enum now has a generic CString variant. While this works, consider if a more descriptive name like Path or PathBuf would be clearer, since it's specifically used for filesystem paths. However, this is subjective and the current naming is acceptable.


🔒 Security Considerations

TOCTOU Handling: The implementation correctly uses AT_FDCWD with the path, which is standard for unlinkat. While TOCTOU (Time-of-check-time-of-use) issues are inherent to path-based operations, this matches the standard library's approach.

Path Handling: Using cstr(path)? properly validates the path and converts it to a null-terminated string, preventing path traversal or null byte injection issues.

No Unsafe Beyond io-uring: The unsafe blocks are properly scoped to Op::new with clear safety comments about parameter validity.


Performance Considerations

Path Ownership: Good optimization in remove_file.rs and remove_dir.rs - you now only clone the path (path.to_owned()) when falling back to the blocking path, not when using io-uring. This reduces allocations in the fast path.

Zero-Copy Path: The io-uring path passes &Path directly and converts to CString only once, which is efficient.


📝 Style & Best Practices

Follows tokio conventions: The conditional compilation, module structure, and trait implementations all follow established tokio patterns.

Proper use of cfg_io_uring! macro: The code correctly uses the configuration macros to conditionally compile io-uring code.


Summary

This is a solid implementation that correctly follows tokio's io-uring patterns. The main issue is the lack of test coverage for the new functionality. Once tests are added and the documentation comment is fixed, this should be ready to merge.

Required before merge:

  • Add comprehensive tests in tokio/tests/fs_uring.rs or similar
  • Fix documentation comment in unlink.rs:35

Nice to have:

  • Verify error handling for edge cases
  • Consider adding a benchmark to demonstrate performance improvement

Review performed with analysis of tokio codebase patterns, io-uring safety requirements, and POSIX unlinkat semantics.

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: 1

🧹 Nitpick comments (1)
tokio/src/fs/remove_file.rs (1)

34-44: LGTM!

The helper functions are correctly implemented with appropriate feature gating.

The io-uring availability check logic (lines 16-29) is duplicated between remove_file.rs and remove_dir.rs. Consider extracting a helper macro or function if this pattern will be reused in additional fs operations (e.g., rename, mkdir, etc.). For now, this duplication is acceptable given only two call sites.

📜 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 8033e6d.

📒 Files selected for processing (6)
  • tokio/src/fs/remove_dir.rs
  • tokio/src/fs/remove_file.rs
  • tokio/src/io/uring/mod.rs
  • tokio/src/io/uring/open.rs
  • tokio/src/io/uring/unlink.rs
  • tokio/src/runtime/driver/op.rs
🧰 Additional context used
🧠 Learnings (1)
📚 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/remove_file.rs
🧬 Code graph analysis (4)
tokio/src/io/uring/mod.rs (1)
tokio/src/io/uring/unlink.rs (1)
  • unlink (37-48)
tokio/src/io/uring/unlink.rs (3)
tokio/src/io/uring/utils.rs (1)
  • cstr (4-6)
tokio/src/io/uring/open.rs (3)
  • complete (22-25)
  • complete_with_error (27-29)
  • cancel (33-35)
tokio/src/runtime/driver/op.rs (4)
  • complete (115-115)
  • complete_with_error (121-121)
  • cancel (126-126)
  • new (64-71)
tokio/src/fs/remove_file.rs (2)
tokio/src/runtime/handle.rs (1)
  • current (144-148)
tokio/src/io/uring/unlink.rs (1)
  • remove_file (50-52)
tokio/src/fs/remove_dir.rs (2)
tokio/src/runtime/handle.rs (1)
  • current (144-148)
tokio/src/io/uring/unlink.rs (1)
  • remove_dir (54-56)
⏰ 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 (10)
tokio/src/runtime/driver/op.rs (1)

4-4: LGTM! Clean generalization of cancellation data.

The refactoring from CancelData::Open(Open) to CancelData::CString(CString) is a sensible abstraction. Both Open and the new Unlink operations need to keep their path alive during cancellation, so using CString directly in the cancellation data avoids duplication and allows multiple operation types to share the same cancellation mechanism.

Also applies to: 21-21

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

32-36: LGTM!

The cancellation now correctly preserves only the CString path, which is what the kernel needs to remain valid. This aligns with the CancelData refactoring and is more precise than keeping the entire Open struct.

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

1-5: LGTM!

Clean module addition following the established pattern for io-uring operations.

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

9-28: LGTM! Clean runtime-conditional io-uring path.

The implementation correctly:

  1. Checks feature flags at compile time via #[cfg(...)]
  2. Verifies io-uring availability at runtime via check_and_init()?
  3. Falls back to blocking implementation when io-uring is unavailable

30-39: Straightforward helper functions.

The separation between remove_dir_uring and remove_dir_blocking is clean. The blocking path correctly clones the path to move into the closure.

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

9-15: LGTM! Struct design follows the established pattern.

The Unlink struct correctly mirrors the Open struct design, keeping the CString path alive for the kernel to read during the operation.


17-27: LGTM!

The Completable implementation is correct. Using .map(drop) elegantly converts the Result<u32> (where u32 is 0 on success) to Result<()>.


29-33: LGTM!

Cancellation correctly preserves the path CString so the kernel can safely complete or cancel the in-flight operation.


50-56: LGTM! Clean convenience wrappers.

The remove_file and remove_dir methods provide clear, intention-revealing names that delegate to the underlying unlink with appropriate flags.

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

13-32: LGTM! Consistent with remove_dir implementation.

The runtime-conditional io-uring path follows the same pattern as remove_dir.rs, which is good for consistency and maintainability.

Comment on lines +35 to +48
impl Op<Unlink> {
/// Submit a request to open a file.
pub(crate) fn unlink(path: &Path, directory: bool) -> io::Result<Op<Unlink>> {
let path = cstr(path)?;

let flags = if directory { libc::AT_REMOVEDIR } else { 0 };

let unlink_op = opcode::UnlinkAt::new(types::Fd(libc::AT_FDCWD), path.as_ptr())
.flags(flags)
.build();

// SAFETY: Parameters are valid for the entire duration of the operation
Ok(unsafe { Op::new(unlink_op, Unlink { path }) })
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the doc comment - it describes the wrong operation.

The doc comment says "Submit a request to open a file" but this function performs an unlink operation.

Proposed fix
 impl Op<Unlink> {
-    /// Submit a request to open a file.
+    /// Submit a request to unlink a file or directory.
     pub(crate) fn unlink(path: &Path, directory: bool) -> io::Result<Op<Unlink>> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
impl Op<Unlink> {
/// Submit a request to open a file.
pub(crate) fn unlink(path: &Path, directory: bool) -> io::Result<Op<Unlink>> {
let path = cstr(path)?;
let flags = if directory { libc::AT_REMOVEDIR } else { 0 };
let unlink_op = opcode::UnlinkAt::new(types::Fd(libc::AT_FDCWD), path.as_ptr())
.flags(flags)
.build();
// SAFETY: Parameters are valid for the entire duration of the operation
Ok(unsafe { Op::new(unlink_op, Unlink { path }) })
}
impl Op<Unlink> {
/// Submit a request to unlink a file or directory.
pub(crate) fn unlink(path: &Path, directory: bool) -> io::Result<Op<Unlink>> {
let path = cstr(path)?;
let flags = if directory { libc::AT_REMOVEDIR } else { 0 };
let unlink_op = opcode::UnlinkAt::new(types::Fd(libc::AT_FDCWD), path.as_ptr())
.flags(flags)
.build();
// SAFETY: Parameters are valid for the entire duration of the operation
Ok(unsafe { Op::new(unlink_op, Unlink { path }) })
}
🤖 Prompt for AI Agents
In tokio/src/io/uring/unlink.rs around lines 35 to 48, the doc comment
incorrectly reads "Submit a request to open a file"; update it to accurately
describe the function (e.g., "Submit a request to unlink (remove) a file or
directory") and mention that the `directory` flag toggles AT_REMOVEDIR to remove
directories; keep the rest of the implementation unchanged.

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:good-to-have; category:documentation; feedback:The CodeRabbit AI reviewer is correct! The docstring is copy/pasted from another file (uring/read.rs) and it is not correct. Prevents releasing a new version with wrong documentation.

@augmentcode
Copy link

augmentcode bot commented Dec 29, 2025

🤖 Augment PR Summary

Summary: Adds an io-uring-backed unlink operation to support async file and directory removal on Linux.

Changes:

  • Routes tokio::fs::remove_file / remove_dir through an io-uring fast path when the driver is available, falling back to spawn_blocking otherwise.
  • Introduces a new io-uring UnlinkAt-based op (io/uring/unlink.rs) with directory support via AT_REMOVEDIR.
  • Simplifies io-uring cancellation bookkeeping by storing raw CString in CancelData (used by open/unlink).

Technical Notes: The io-uring path is gated behind tokio_unstable + io-uring/rt/fs on Linux and uses Handle::current().inner.driver().io().check_and_init() to decide the fast path.

🤖 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. 1 suggestions posted.

Fix All in Augment

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

}

impl Op<Unlink> {
/// Submit a request to open a file.
Copy link

Choose a reason for hiding this comment

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

This doc comment says “open a file”, but this function submits an UnlinkAt request (used for remove_file/remove_dir); updating it would avoid misleading readers.

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:good-to-have; category:documentation; feedback:The Augment AI reviewer is correct! The docstring is copy/pasted from another file (uring/read.rs) and it is not correct. Prevents releasing a new version with wrong documentation.

@martin-augment
Copy link
Owner Author

1. Missing Test Coverage (High Priority)

The PR does not include any tests for the new io-uring unlink functionality. Looking at tokio/tests/fs_uring.rs, there's a clear pattern for testing io-uring features.

value:useful; category:bug; feedback:The Claude AI reviewer is correct! New integration tests should be added to prevent regressions in the future. There are some old tests for the non-io_uring file system operations which are automatically enabled when io_uring is enabled.

@martin-augment
Copy link
Owner Author

2. Incomplete Documentation (Medium Priority)

In tokio/src/io/uring/unlink.rs:35: The doc comment says "Submit a request to open a file" but it should say "Submit a request to unlink a file/directory".

-    /// Submit a request to open a file.
+    /// Submit a request to unlink a file or directory.

value:good-to-have; category:documentation; feedback:The Claude AI reviewer is correct! The docstring is copy/pasted from another file (uring/read.rs) and it is not correct. Prevents releasing a new version with wrong documentation.

@martin-augment
Copy link
Owner Author

4. Code Consistency: CancelData Pattern (Low Priority)

The CancelData enum now has a generic CString variant. While this works, consider if a more descriptive name like Path or PathBuf would be clearer, since it's specifically used for filesystem paths. However, this is subjective and the current naming is acceptable.

value:incorrect-but-reasonable; category:bug; feedback:The Claude AI reviewer is correct! Actually the new enum variant should be named Unlink and be used only by the unlink module. Prevents introducing a generic variant that will be used by more than one module in inconsistent ways.

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