Skip to content

7847: fs: implement tokio::fs::create_dir[_all] via io_uring#71

Open
martin-augment wants to merge 1 commit intomasterfrom
pr-7847-2026-03-04-07-25-49
Open

7847: fs: implement tokio::fs::create_dir[_all] via io_uring#71
martin-augment wants to merge 1 commit intomasterfrom
pr-7847-2026-03-04-07-25-49

Conversation

@martin-augment
Copy link
Copy Markdown
Owner

7847: To review by AI

@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 significantly enhances Tokio's file system capabilities by integrating io_uring support for directory creation functions, specifically create_dir and create_dir_all. This change aims to improve the efficiency and performance of these operations on Linux by leveraging the kernel's asynchronous I/O interface, providing a more robust and scalable solution for file system interactions within Tokio applications.

Highlights

  • io_uring Integration for create_dir: The tokio::fs::create_dir function now conditionally utilizes io_uring for directory creation on Linux systems when the tokio_unstable, io-uring, rt, and fs features are enabled. This provides a performance optimization by leveraging asynchronous I/O.
  • io_uring Integration for create_dir_all: The tokio::fs::create_dir_all function has been updated to also conditionally use io_uring for creating directories and their parent directories on Linux under the same feature flags. A new internal module, create_dir_all_uring, was introduced to handle the specific logic for this operation.
  • New Mkdir io_uring Operation: A new Mkdir operation has been added to the tokio::io::uring module, allowing io_uring to directly handle mkdir system calls. This includes its Completable and Cancellable implementations for proper asynchronous handling and cancellation.
  • Comprehensive Testing: New tests have been added to validate the io_uring based directory creation, covering scenarios such as runtime shutdown during operations, creating a large number of directories, edge cases for create_dir_all, and cancellation of mkdir operations.
Changelog
  • tokio/src/fs/create_dir.rs
    • Added conditional io_uring implementation for create_dir.
  • tokio/src/fs/create_dir_all.rs
    • Added conditional io_uring implementation for create_dir_all, delegating to a new create_dir_all_uring function.
  • tokio/src/fs/create_dir_all_uring.rs
    • Added new module containing io_uring specific logic for create_dir_all.
  • tokio/src/fs/mod.rs
    • Imported the new create_dir_all_uring module and function.
  • tokio/src/io/uring/mkdir.rs
    • Added new module defining the Mkdir operation for io_uring.
  • tokio/src/io/uring/mod.rs
    • Declared the new mkdir module.
  • tokio/src/runtime/driver/op.rs
    • Modified CancelData enum to include Mkdir for cancellation support.
  • tokio/tests/fs_uring_mkdir.rs
    • Added new tests for io_uring based directory creation and cancellation.
Activity
  • The pull request was opened by martin-augment with the title "7847: fs: implement tokio::fs::create_dir[_all] via io_uring" and a description "7847: To 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 4, 2026

Walkthrough

This change introduces io-uring optimized directory creation operations for Tokio on Linux. The create_dir function now attempts to use io-uring's MkDirAt operation when available, falling back to asyncify. The create_dir_all function gains a conditional Linux-specific fast path delegating to a new create_dir_all_uring module that creates directories and parent chains using io-uring. Supporting infrastructure includes a new Mkdir io-uring operation type, updates to the cancellation data structure, and comprehensive test coverage for concurrent directory creation, runtime shutdown, and operation cancellation scenarios.

✨ 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-7847-2026-03-04-07-25-49

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 introduces io_uring support for tokio::fs::create_dir and tokio::fs::create_dir_all on Linux, which is a significant performance improvement. The implementation correctly integrates with the io_uring driver and includes a new create_dir_all_uring module to handle recursive directory creation. Comprehensive tests have been added to cover various scenarios, including runtime shutdown and edge cases for create_dir_all.

However, there are a few areas that could be improved for robustness, performance consistency, and idiomatic Rust path handling.

Ok(()) => Ok(true),
Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
// TODO: replace with uring-based statx
Err(_) if crate::fs::metadata(path).await?.is_dir() => Ok(true),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The fallback to crate::fs::metadata here, and similarly on line 88, can introduce blocking operations if io-uring is not enabled for metadata itself. This would negate the performance benefits of using io-uring for mkdir in these specific error handling paths. While the TODO comment acknowledges the need for uring-based statx, the current implementation can lead to unexpected blocking.

Consider ensuring that all file system operations within the io-uring path are non-blocking or handle the blocking fallback explicitly with spawn_blocking to avoid blocking the io-uring driver thread.

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-but-wont-fix; category:bug; feedback: The Gemini AI reviewer is correct! The statx (file metadata) is not yet implemented for io-uring, but IO_Uring support is introduced into Tokio feature by feature. This PR is about create_dir. A follow-up PR should be about statx

Comment on lines +33 to +43
let path_bytes = path.as_os_str().as_bytes();
for (separator_pos, _) in path_bytes
.iter()
.enumerate()
.rev()
.filter(|(_, &byte)| byte == b'/')
{
let parent_bytes = &path_bytes[..separator_pos];
let parent_str = OsStr::from_bytes(parent_bytes);
let parent_path = Path::new(parent_str);
if mkdir_parent_missing(parent_path).await? {
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

The manual byte-level manipulation of paths using as_os_str().as_bytes() and OsStr::from_bytes() for splitting and rejoining path components is less idiomatic and potentially more error-prone than using Path's built-in methods like parent() and components(). While it might work correctly for typical Unix paths, using Path methods would improve readability, maintainability, and robustness against edge cases (e.g., root paths, multiple slashes, or future platform changes).

For example, path.parent() and then iterating over path.components() could provide a more robust way to handle path segments.

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 Gemini AI reviewer is correct! There is no need to do this manual parsing of the file path. The PR author should use std::fs::Path and PathBuf instead.

Comment on lines +56 to +57
// TODO: We're attempting to create all directories sequentially.
// This would benefit from batching.
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

The TODO comment correctly identifies a significant optimization opportunity. Creating directories sequentially can be inefficient, especially for deep paths. Batching these operations, if io-uring supports it (e.g., via IORING_OP_MKDIRAT with IORING_ASYNC_BATCH), would greatly improve performance for create_dir_all.

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! If there are many directories to be created using batching may be better but this should be benchmarked first and implemented in a follow-up if needed.

@claude
Copy link
Copy Markdown

claude bot commented Mar 4, 2026

PR Review: fs: implement tokio::fs::create_dir[_all] via io_uring

Good overall structure - the implementation follows the existing patterns for io_uring operations (open, read, write). A few issues ranging from a correctness bug to naming and style concerns.


Bug: Error information is lost in mkdir_parent_missing / mkdir_parent_created

In create_dir_all_uring.rs, both helper functions use a catch-all pattern before checking metadata:

Err(_) if crate::fs::metadata(path).await?.is_dir() => Ok(true),

The problem: Err(_) discards the original error. If Op::mkdir(path) fails for a non-NotFound reason (e.g. EACCES because the parent directory has no write permission), we then call metadata(path). Since mkdir failed and the path does not exist, metadata(path) returns ENOENT. The ? propagates that ENOENT, silently replacing the original EACCES.

Concrete scenario:

  • mkdir("/no-write-perm/child") returns EACCES (parent is not writable)
  • metadata("/no-write-perm/child") returns ENOENT (child does not exist)
  • Caller receives ENOENT instead of the correct EACCES

A more robust pattern is to only check metadata when the mkdir error is AlreadyExists/EEXIST, since that is the only case where the path already existing as a directory is a plausible success:

Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
    if crate::fs::metadata(path).await?.is_dir() {
        Ok(true)
    } else {
        Err(io::Error::from_raw_os_error(libc::EEXIST))
    }
}
Err(e) => Err(e),

Naming confusion: mkdir_parent_missing creates directories as a side effect

The name mkdir_parent_missing implies a predicate ("is the parent missing?"), but the function actually creates the target directory as a side effect and returns Ok(true) if creation succeeded or the directory already exists. This makes the reverse-scan algorithm hard to follow since readers would not expect a "check" function to mutate state.

Consider renaming to something like try_mkdir and updating comments to be explicit that the directory is created in-place during the reverse pass.


Redundant cfg guard in create_dir_all_uring.rs

The use crate::runtime::driver::op::Op; import has a cfg guard, but the functions below that use Op are not guarded. Since the entire module is only included via cfg_io_uring! in fs/mod.rs, the guard on the use statement is redundant and misleading. Remove the cfg from the use statement.


Import style inconsistency in mkdir.rs

open.rs (the most analogous existing module) uses:

use std::io::{self, Error};

mkdir.rs uses two separate statements:

use std::io;
use std::io::Error;

Prefer the combined form to match the existing codebase style.


Algorithm: redundant mkdir calls for paths with trailing slashes

For a path like /baz/qux//, the byte-level iteration finds / at positions 4, 8, and 9. During the reverse pass, mkdir("/baz/qux") is called (position 8), creating the directory. Then in the forward pass, the / at position 9 causes mkdir_parent_created("/baz/qux/") to be called - an extra attempt on the same logical path. The is_dir fallback handles this gracefully, but it results in an unnecessary round-trip. Using Path::components() based iteration would naturally skip duplicate/trailing slashes.


Test: duplicated runtime helpers

fs_uring_mkdir.rs copy-pastes multi_rt, current_rt, and rt_combinations verbatim from fs_uring.rs. These should be extracted into a shared test utility module to avoid maintenance drift.


Test: shutdown_runtime_while_performing_io_uring_ops may be fragile

The test relies on a 300ms wall-clock timeout. With multi_rt(256) spawning tasks in a tight loop, this can be slow or flaky in CI under load. Consider reducing the thread counts in this specific test, or using a synchronization primitive to signal readiness before shutdown.


Missing test coverage

  • Path component is a file: create_dir_all("/existing_file/child") should return an error. No test covers this for the uring path.
  • Permission denied: No test for EACCES propagation.
  • Concurrent create_dir_all: The std docs guarantee safety under concurrent calls. A stress test for concurrent calls on the same path would give more confidence in the EEXIST/is_dir fallback logic.

Minor: MkDirAt mode 0o777

Using 0o777 is correct (matches what std::fs::create_dir does; the kernel applies umask). Worth adding a brief comment in mkdir.rs clarifying that effective permissions are subject to umask, to prevent future confusion about why created directories do not always have 0o777 permissions.


Overall the implementation is well-structured and follows existing io_uring conventions. The most important issue to address before merging is the error-swallowing bug in the Err(_) catch-all pattern, which can return the wrong error kind to callers.

@augmentcode
Copy link
Copy Markdown

augmentcode bot commented Mar 4, 2026

🤖 Augment PR Summary

Summary: Adds io_uring-backed implementations for tokio::fs::create_dir and tokio::fs::create_dir_all on Linux (behind tokio_unstable + io-uring + rt + fs).

Changes:

  • Routes create_dir to a new io_uring MkDirAt operation when the runtime supports it; otherwise falls back to the existing blocking-threadpool path.
  • Routes create_dir_all to a new create_dir_all_uring helper when MkDirAt is supported; otherwise falls back to the threadpool implementation.
  • Introduces tokio::io::uring::mkdir and wires it into the driver’s cancellation bookkeeping via CancelData::Mkdir.
  • Adds tokio::fs::create_dir_all_uring, which creates missing parents by issuing sequential mkdir ops and using metadata to treat pre-existing directories as success.
  • Updates the fs module to include/export the new uring helper under cfg_io_uring!.
  • Adds a new test suite (fs_uring_mkdir.rs) covering runtime shutdown with in-flight mkdir ops, creating many directories concurrently, create_dir_all edge cases, and operation cancellation.

Technical Notes: Uses io_uring::opcode::MkDirAt with AT_FDCWD and mode 0o777, with runtime feature-gated initialization via check_and_init.

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

))]
{
let handle = crate::runtime::Handle::current();
let driver_handle = handle.inner.driver().io();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

handle.inner.driver().io() will panic if the runtime was built without IO enabled (i.e., enable_io/enable_all not called), which would be a behavior change vs the asyncify fallback. Consider guarding this so create_dir still falls back cleanly when IO is disabled at runtime.

Severity: high

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:good-but-wont-fix; category:bug; feedback: The Augment AI reviewer is correct! io_uring support is experimental/unstable in Tokio, so the developer should opt-in for it. It is an application error if io_uring is requested but the IO driver is not enabled. Returning the error would tell the developer what (s)he needs to do. Falling back to asyncify() will hide the problem and leave the developer believing that IO Uring is in use.

use crate::fs::create_dir_all_uring;

let handle = crate::runtime::Handle::current();
let driver_handle = handle.inner.driver().io();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same concern as create_dir: calling handle.inner.driver().io() can panic when the runtime has IO disabled, preventing the intended asyncify fallback. It may be worth ensuring create_dir_all doesn’t require IO-enabled runtimes just to decide whether to use io_uring.

Severity: high

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:good-but-wont-fix; category:bug; feedback: The Augment AI reviewer is correct! io_uring support is experimental/unstable in Tokio, so the developer should opt-in for it. It is an application error if io_uring is requested but the IO driver is not enabled. Returning the error would tell the developer what (s)he needs to do. Falling back to asyncify() will hide the problem and leave the developer believing that IO Uring is in use.

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tokio/src/fs/create_dir_all_uring.rs`:
- Around line 73-79: The metadata fallback in mkdir_parent_missing currently
uses the `?` operator inside an `Err(...) if ...` guard, causing any metadata
error to replace the original `Op::mkdir` error; change the guard to capture the
original mkdir error (from `Op::mkdir(path)?.await`), then explicitly call
`crate::fs::metadata(path).await` in a separate match/if-let so you can inspect
its Result: on Ok(meta) if meta.is_dir() return Ok(true), on Err(_) return the
original mkdir error (Err(original_err)); update both occurrences that use this
pattern so metadata failures do not clobber the original `Op::mkdir` error.

In `@tokio/tests/fs_uring_mkdir.rs`:
- Around line 146-153: The test recreates the create_dir future on every poll
because `fut` is declared inside the `poll_fn` closure; move the one-shot
operation out so the same in-flight future is polled across invocations.
Concretely, allocate and pin the `tokio::fs::create_dir(&child)` future once
(e.g., a `let mut fut = Box::pin(...)` or `Option<Pin<_>>`), then have the
`poll_fn` closure poll that pinned `fut` via `poll_unpin` instead of reassigning
a new `fut` each poll; update the `cancel_op_future` test harness to reference
this single pinned future so the cancellation behavior is exercised correctly.
- Around line 60-67: The test currently re-executes assert_pending! on every
re-poll inside the poll_fn closure, which can panic when the underlying
create_dir future becomes ready and the task is detached; fix by ensuring the
“pending” assertion runs only once before readiness detection—e.g., capture a
local mutable bool (e.g., asserted) in the poll_fn closure or perform a single
assert_pending! call before entering the poll loop, then on subsequent polls
check fut.as_mut().poll(cx) == Poll::Pending without reasserting; reference the
poll_fn closure, assert_pending!, and fut (the create_dir future) when applying
the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6887a0c8-72b5-4051-82ee-211c908ee3ca

📥 Commits

Reviewing files that changed from the base of the PR and between b3f1a84 and ea71192.

📒 Files selected for processing (8)
  • tokio/src/fs/create_dir.rs
  • tokio/src/fs/create_dir_all.rs
  • tokio/src/fs/create_dir_all_uring.rs
  • tokio/src/fs/mod.rs
  • tokio/src/io/uring/mkdir.rs
  • tokio/src/io/uring/mod.rs
  • tokio/src/runtime/driver/op.rs
  • tokio/tests/fs_uring_mkdir.rs

Comment on lines +73 to +79
async fn mkdir_parent_missing(path: &Path) -> io::Result<bool> {
match Op::mkdir(path)?.await {
Ok(()) => Ok(true),
Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
// TODO: replace with uring-based statx
Err(_) if crate::fs::metadata(path).await?.is_dir() => Ok(true),
Err(e) => Err(e),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Metadata fallback currently clobbers the original mkdir error.

In Lines 78 and 87, metadata is evaluated with ? in a guard. If metadata fails, that metadata error is returned instead of the original Op::mkdir failure.

Suggested fix
 async fn mkdir_parent_missing(path: &Path) -> io::Result<bool> {
     match Op::mkdir(path)?.await {
         Ok(()) => Ok(true),
         Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
         // TODO: replace with uring-based statx
-        Err(_) if crate::fs::metadata(path).await?.is_dir() => Ok(true),
-        Err(e) => Err(e),
+        Err(e) => match crate::fs::metadata(path).await {
+            Ok(metadata) if metadata.is_dir() => Ok(true),
+            _ => Err(e),
+        },
     }
 }
 
 async fn mkdir_parent_created(path: &Path) -> io::Result<()> {
     match Op::mkdir(path)?.await {
         Ok(()) => Ok(()),
         // TODO: replace with uring-based statx
-        Err(_) if crate::fs::metadata(path).await?.is_dir() => Ok(()),
-        Err(e) => Err(e),
+        Err(e) => match crate::fs::metadata(path).await {
+            Ok(metadata) if metadata.is_dir() => Ok(()),
+            _ => Err(e),
+        },
     }
 }

Also applies to: 83-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tokio/src/fs/create_dir_all_uring.rs` around lines 73 - 79, The metadata
fallback in mkdir_parent_missing currently uses the `?` operator inside an
`Err(...) if ...` guard, causing any metadata error to replace the original
`Op::mkdir` error; change the guard to capture the original mkdir error (from
`Op::mkdir(path)?.await`), then explicitly call
`crate::fs::metadata(path).await` in a separate match/if-let so you can inspect
its Result: on Ok(meta) if meta.is_dir() return Ok(true), on Err(_) return the
original mkdir error (Err(original_err)); update both occurrences that use this
pattern so metadata failures do not clobber the original `Op::mkdir` error.

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 CodeRabbit AI reviewer is correct! If the metadata() calls errors then it will hide the original error. Better error handling is needed in this case to combine the errors or at least show the original error.

Comment on lines +60 to +67
poll_fn(|cx| {
assert_pending!(fut.as_mut().poll(cx));
Poll::<()>::Pending
})
.await;

fut.await.unwrap();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

assert_pending! is re-run on wake and can silently panic detached tasks.

At Line 61, the assertion is executed on every poll_fn re-poll. Once create_dir becomes ready, this panics inside a detached task, so the test can still pass while work panics.

Suggested fix
-                    poll_fn(|cx| {
-                        assert_pending!(fut.as_mut().poll(cx));
-                        Poll::<()>::Pending
-                    })
-                    .await;
-
-                    fut.await.unwrap();
+                    let mut first_poll = true;
+                    poll_fn(|cx| {
+                        if first_poll {
+                            assert_pending!(fut.as_mut().poll(cx));
+                            first_poll = false;
+                        }
+                        Poll::<()>::Pending
+                    })
+                    .await;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tokio/tests/fs_uring_mkdir.rs` around lines 60 - 67, The test currently
re-executes assert_pending! on every re-poll inside the poll_fn closure, which
can panic when the underlying create_dir future becomes ready and the task is
detached; fix by ensuring the “pending” assertion runs only once before
readiness detection—e.g., capture a local mutable bool (e.g., asserted) in the
poll_fn closure or perform a single assert_pending! call before entering the
poll loop, then on subsequent polls check fut.as_mut().poll(cx) == Poll::Pending
without reasserting; reference the poll_fn closure, assert_pending!, and fut
(the create_dir future) when applying the change.

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 CodeRabbit AI reviewer is correct! The test assumes that the poll_fn will be called just once because the handle should be aborted after the first poll but due to timings the poll_fn may be called more than once and the assertion will fail on the second run. The test should be improved to not depend on racing.

Comment on lines +146 to +153
poll_fn(|cx| {
let child = workdir.path().join("child");
let fut = tokio::fs::create_dir(&child);

// If io_uring is enabled (and not falling back to the thread pool),
// the first poll should return Pending.
let _pending = pin!(fut).poll_unpin(cx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

cancel_op_future recreates/drops the op each poll instead of holding one in-flight future.

Because fut is local to the closure, it is dropped every poll cycle. This weakens the cancellation test intent for a single operation future.

Suggested fix
-        poll_fn(|cx| {
-            let child = workdir.path().join("child");
-            let fut = tokio::fs::create_dir(&child);
-
-            // If io_uring is enabled (and not falling back to the thread pool),
-            // the first poll should return Pending.
-            let _pending = pin!(fut).poll_unpin(cx);
-
-            tx.send(()).unwrap();
-
-            Poll::<()>::Pending
-        })
+        let child = workdir.path().join("child");
+        let mut fut = pin!(tokio::fs::create_dir(&child));
+        let mut first_poll = true;
+        poll_fn(|cx| {
+            if first_poll {
+                assert_pending!(fut.as_mut().poll(cx));
+                first_poll = false;
+                tx.send(()).unwrap();
+            }
+            Poll::<()>::Pending
+        })
         .await;
📝 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
poll_fn(|cx| {
let child = workdir.path().join("child");
let fut = tokio::fs::create_dir(&child);
// If io_uring is enabled (and not falling back to the thread pool),
// the first poll should return Pending.
let _pending = pin!(fut).poll_unpin(cx);
let child = workdir.path().join("child");
let mut fut = pin!(tokio::fs::create_dir(&child));
let mut first_poll = true;
poll_fn(|cx| {
if first_poll {
assert_pending!(fut.as_mut().poll(cx));
first_poll = false;
tx.send(()).unwrap();
}
Poll::<()>::Pending
})
.await;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tokio/tests/fs_uring_mkdir.rs` around lines 146 - 153, The test recreates the
create_dir future on every poll because `fut` is declared inside the `poll_fn`
closure; move the one-shot operation out so the same in-flight future is polled
across invocations. Concretely, allocate and pin the
`tokio::fs::create_dir(&child)` future once (e.g., a `let mut fut =
Box::pin(...)` or `Option<Pin<_>>`), then have the `poll_fn` closure poll that
pinned `fut` via `poll_unpin` instead of reassigning a new `fut` each poll;
update the `cancel_op_future` test harness to reference this single pinned
future so the cancellation behavior is exercised correctly.

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 CodeRabbit AI reviewer is correct! The test assumes that the poll_fn will be called just once because the handle should be aborted after the first poll but due to timings the poll_fn may be called more than once and then the a new future will be created. This does not do any harm but it is confusing

@martin-augment
Copy link
Copy Markdown
Owner Author

Bug: Error information is lost in mkdir_parent_missing / mkdir_parent_created

In create_dir_all_uring.rs, both helper functions use a catch-all pattern before checking metadata:

Err(_) if crate::fs::metadata(path).await?.is_dir() => Ok(true),

The problem: Err(_) discards the original error. If Op::mkdir(path) fails for a non-NotFound reason (e.g. EACCES because the parent directory has no write permission), we then call metadata(path). Since mkdir failed and the path does not exist, metadata(path) returns ENOENT. The ? propagates that ENOENT, silently replacing the original EACCES.

Concrete scenario:

  • mkdir("/no-write-perm/child") returns EACCES (parent is not writable)
  • metadata("/no-write-perm/child") returns ENOENT (child does not exist)
  • Caller receives ENOENT instead of the correct EACCES

A more robust pattern is to only check metadata when the mkdir error is AlreadyExists/EEXIST, since that is the only case where the path already existing as a directory is a plausible success:

Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
    if crate::fs::metadata(path).await?.is_dir() {
        Ok(true)
    } else {
        Err(io::Error::from_raw_os_error(libc::EEXIST))
    }
}
Err(e) => Err(e),

value:useful; category:bug; feedback: The Claude AI reviewer is correct! If the metadata read operation fails it will hide the original error and the developer won't have enough information to debug the problem. The logic should be improved to return both errors if the metadata read fails.

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