Skip to content

7844: fs: implement tokio::fs::symlink via io_uring#49

Open
martin-augment wants to merge 1 commit intomasterfrom
pr-7844-2026-01-09-06-44-59
Open

7844: fs: implement tokio::fs::symlink via io_uring#49
martin-augment wants to merge 1 commit intomasterfrom
pr-7844-2026-01-09-06-44-59

Conversation

@martin-augment
Copy link
Owner

7844: To review by AI

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Walkthrough

This change implements io-uring based symlink operations in Tokio for Linux systems when the tokio_unstable, io-uring, rt, and fs features are enabled. A new Symlink struct is added to the io/uring module to manage the operation data. The implementation includes trait implementations for Completable and Cancellable, enabling proper lifecycle management and cancellation handling. The filesystem symlink function is updated with a conditional fast path that uses the io-uring driver when available, falling back to the standard asyncify approach otherwise. Cancellation support is integrated into the runtime's driver. A new test suite validates the functionality across multiple runtime configurations and tests shutdown behavior, correctness of many concurrent operations, and cancellation semantics.

✨ Finishing touches
  • 📝 Generate docstrings

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

@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 (3)
tokio/tests/fs_uring_symlink.rs (2)

50-89: Test logic is correct for shutdown stress-testing.

The test intentionally spawns operations in an unbounded loop (line 58) to stress-test runtime shutdown behavior with many in-flight io_uring operations. The shutdown_timeout ensures the test completes within 300ms.

Optional: Add clarifying comment about intentional loop behavior
         rt.spawn(async move {
-            // spawning a bunch of uring operations.
+            // Intentionally spawn an unbounded number of operations to stress-test
+            // shutdown behavior with many in-flight io_uring ops.
             for i in 0..usize::MAX {

128-156: Consider clarifying the expected poll behavior.

The test correctly validates cancellation semantics, but there's a minor inconsistency: the comment on lines 138-139 states "the first poll should return Pending," yet line 140 doesn't validate this expectation.

If the io_uring path is guaranteed to return Pending on first poll, consider asserting this:

-            // 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);
+            // When using io_uring (not falling back to the thread pool),
+            // the first poll returns Pending.
+            let poll_result = pin!(fut).poll_unpin(cx);
+            assert!(poll_result.is_pending());

Alternatively, if the behavior varies based on whether io_uring is actually used, the comment could be updated to reflect that this is expected but not enforced.

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

41-54: LGTM! Implementation is correct and safe.

The constructor properly:

  • Converts paths to null-terminated C strings
  • Uses correct argument order for SymlinkAt (dirfd, target, linkpath)
  • Ensures pointer validity by moving CStrings into the Symlink struct, which is owned by the Op for the operation's lifetime

The safety invariant is maintained: the CString instances (and thus their pointers) remain valid because they're owned by the Symlink struct, which in turn is owned by the Op that must stay alive until the io_uring operation completes.

Optional: Enhance safety documentation for clarity

Consider expanding the safety comment to be more explicit about the ownership chain:

-        // SAFETY: Parameters are valid for the entire duration of the operation
+        // SAFETY: The CString pointers remain valid for the operation's lifetime because
+        // the CStrings are moved into the Symlink struct, which is owned by the Op. The Op
+        // must remain alive until the io_uring operation completes, ensuring pointer validity.
         Ok(unsafe { Op::new(symlink_op, Symlink { original, link }) })
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fe52b2 and 5b5d8f4.

📒 Files selected for processing (5)
  • tokio/src/fs/symlink.rs
  • tokio/src/io/uring/mod.rs
  • tokio/src/io/uring/symlink.rs
  • tokio/src/runtime/driver/op.rs
  • tokio/tests/fs_uring_symlink.rs
🧰 Additional context used
🧠 Learnings (1)
📚 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/tests/fs_uring_symlink.rs
🧬 Code graph analysis (5)
tokio/src/runtime/driver/op.rs (2)
tokio/src/fs/symlink.rs (1)
  • symlink (11-31)
tokio/src/io/uring/symlink.rs (1)
  • symlink (43-53)
tokio/src/fs/symlink.rs (2)
tokio/src/runtime/handle.rs (1)
  • current (144-148)
tokio/src/io/uring/symlink.rs (1)
  • symlink (43-53)
tokio/src/io/uring/mod.rs (2)
tokio/src/fs/symlink.rs (1)
  • symlink (11-31)
tokio/src/io/uring/symlink.rs (1)
  • symlink (43-53)
tokio/tests/fs_uring_symlink.rs (2)
tokio/src/fs/symlink.rs (1)
  • symlink (11-31)
tokio/src/io/uring/symlink.rs (1)
  • symlink (43-53)
tokio/src/io/uring/symlink.rs (3)
tokio/src/io/uring/utils.rs (1)
  • cstr (4-6)
tokio/src/runtime/driver/op.rs (4)
  • complete (117-117)
  • complete_with_error (123-123)
  • cancel (128-128)
  • new (66-73)
tokio/src/fs/symlink.rs (1)
  • symlink (11-31)
⏰ 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/io/uring/mod.rs (1)

3-3: LGTM! Module declaration follows existing patterns.

The module declaration is consistent with other io_uring operation modules and properly positioned.

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

15-30: LGTM! io_uring fast path with proper fallback.

The implementation correctly:

  • Feature-gates the io_uring path behind appropriate flags
  • Checks driver support before attempting to use io_uring
  • Falls back gracefully to the thread pool-based asyncify approach
  • Maintains the same public API regardless of the execution path
tokio/src/runtime/driver/op.rs (1)

3-3: LGTM! Cancellation support follows established patterns.

The changes correctly integrate symlink into the cancellation infrastructure:

  • Import is properly placed alongside other io_uring operations
  • CancelData variant follows the same pattern as Open, Write, and Read
  • No additional lifecycle handling needed due to the existing generic implementation

Also applies to: 23-23

tokio/tests/fs_uring_symlink.rs (4)

3-9: LGTM! Feature gates are correct.

The test module is properly gated behind the same features as the implementation.


25-48: LGTM! Comprehensive runtime test coverage.

Testing across multiple runtime configurations (current-thread and multi-thread with varying worker counts) ensures the implementation works correctly in different scenarios.


91-126: LGTM! Effective stress test for concurrent operations.

The test validates both correctness and performance with 10,000 concurrent symlink operations, then verifies each symlink resolves correctly.


158-168: LGTM! Helper correctly sets up test fixtures.

The helper creates a temporary directory with a target file for symlink tests, with automatic cleanup via TempDir's Drop implementation.

tokio/src/io/uring/symlink.rs (3)

11-21: LGTM! Struct correctly manages string lifetimes.

The Symlink struct properly owns the CString instances, ensuring the pointers passed to io_uring remain valid for the operation's duration. The #[allow(dead_code)] attributes are appropriate since these fields are accessed by the kernel, not Rust code.


23-33: LGTM! Completable implementation is correct.

The trait implementation properly:

  • Maps successful io_uring results to () (symlink doesn't return meaningful data)
  • Propagates errors through complete_with_error

35-39: LGTM! Cancellable implementation follows the established pattern.

@claude
Copy link

claude bot commented Jan 9, 2026

Pull Request Review: io_uring symlink implementation

Summary

This PR implements tokio::fs::symlink using io_uring's SymlinkAt operation for improved async I/O performance on Linux. The implementation follows the existing patterns established for other io_uring operations (open, read, write) and includes comprehensive test coverage.

✅ Strengths

Code Quality

  • Consistent with existing patterns: The implementation closely follows the structure of other io_uring operations (open.rs, read.rs, write.rs), maintaining code consistency
  • Proper trait implementations: Correctly implements Completable and Cancellable traits as required by the io_uring framework
  • Appropriate use of feature gates: Properly guards io_uring code with the correct cfg attributes
  • Good documentation: Includes helpful comments explaining why fields are kept alive

Test Coverage

  • Comprehensive test suite: Includes shutdown tests, stress tests with 10,000 operations, and cancellation tests
  • Multiple runtime configurations: Tests across various runtime configurations (current_thread, multi_thread with 1/2/8/64/256 workers)
  • Real-world scenarios: Tests cover edge cases like runtime shutdown during pending operations

Safety

  • Correct memory safety: CString fields are properly held to ensure kernel can access them during the operation's lifetime
  • Safe fallback: Falls back to thread pool implementation when io_uring is unavailable or unsupported

🔍 Issues & Suggestions

1. Critical: Infinite loop in shutdown test (tokio/tests/fs_uring_symlink.rs:57)

The loop runs to usize::MAX, which on 64-bit systems is 18,446,744,073,709,551,615. This test will never complete normally.

Recommendation: Use a reasonable constant like other tests do (e.g., 100 or 1000)

2. Test quality: Unnecessary spawning in shutdown test (tokio/tests/fs_uring_symlink.rs:60)

The shutdown test spawns individual tasks for each symlink operation, but never awaits or tracks them. This creates tasks that are abandoned, making the test behavior less predictable.

Recommendation: Consider using a pattern more similar to fs_uring_read.rs:50-75 where futures are stored and managed.

3. Documentation: Missing module-level docs (tokio/src/io/uring/symlink.rs:1)

The file lacks a module-level documentation comment explaining what SymlinkAt does and its semantics.

4. Missing error case tests

Tests are missing for error cases like invalid paths, permission denied, existing files, paths with null bytes, and symlink chains.

🔒 Security Considerations

✅ No Security Issues Found

  • No command injection: Uses proper io_uring syscall, not shell commands
  • No path traversal vulnerabilities: Paths are passed directly to kernel via io_uring
  • Memory safety: CStrings are properly held for operation lifetime
  • No TOCTOU issues: Operation is atomic at kernel level

⚡ Performance Considerations

✅ Good Performance Characteristics

  • Avoids thread pool overhead: Uses io_uring when available instead of asyncify
  • Proper fallback: Falls back to thread pool when io_uring unavailable
  • Correct operation check: Uses check_and_init() to verify kernel support before attempting operation

🎯 Overall Assessment

Status: Approve with minor fixes recommended

The implementation is solid and follows tokio's patterns well. The main concern is the infinite loop in the shutdown test which must be fixed before merge.

Required Changes

  • Fix the infinite loop in shutdown_runtime_while_performing_io_uring_ops

Recommended Changes

  • Improve shutdown test to properly manage spawned tasks
  • Add error case tests
  • Add module and method documentation

Code Quality Score: 8/10

  • Architecture: ✅ Excellent
  • Safety: ✅ Excellent
  • Testing: ⚠️ Good (missing error cases)
  • Documentation: ⚠️ Adequate (could be improved)
  • Performance: ✅ Excellent

Great work on this implementation! The code quality is high and it integrates well with the existing io_uring infrastructure.

@augmentcode
Copy link

augmentcode bot commented Jan 9, 2026

🤖 Augment PR Summary

Summary: This PR adds an io_uring-backed implementation for tokio::fs::symlink on Linux (behind tokio_unstable + feature gates), while retaining the existing blocking-thread fallback.

Changes:

  • Routes tokio::fs::symlink through the runtime’s io_uring driver when SymlinkAt is supported; otherwise falls back to asyncify(... std::os::unix::fs::symlink ...).
  • Adds a new io_uring operation module tokio/src/io/uring/symlink.rs implementing Op<Symlink> via io_uring::opcode::SymlinkAt.
  • Wires the new op into the io_uring module tree and extends cancellation storage via CancelData::Symlink.
  • Adds Linux/io_uring-gated tests exercising bulk symlink creation, cancellation, and runtime shutdown with in-flight symlink ops.

Technical Notes: The io_uring path is enabled only when the runtime + fs + io_uring features are present and the kernel reports SymlinkAt opcode support; otherwise behavior remains the spawn-blocking fallback.

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

multi_rt(2),
multi_rt(8),
multi_rt(64),
multi_rt(256),
Copy link

Choose a reason for hiding this comment

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

Including multi_rt(256) can create hundreds of OS threads during CI, which may exceed resource limits and make this test flaky. Worth considering whether a smaller max thread count would still cover the intended stress scenario.

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:annoying; category:bug; feedback:The Augment AI reviewer is not correct! The Tokio runtimes do not create OS threads for each task! They create num_cpus OS threads and schedule all tasks on them, so the footprint in the Linux kernel memory is kept in boundaries.


rt.spawn(async move {
// spawning a bunch of uring operations.
for i in 0..usize::MAX {
Copy link

Choose a reason for hiding this comment

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

The for i in 0..usize::MAX loop can enqueue an unbounded number of tasks before the shutdown thread fires, which can lead to high memory usage/flake on slower machines. Consider bounding the number of spawned ops or making it time-based to keep the test more deterministic.

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:valid-but-wont-fix; category:bug; feedback:The Augment AI reviewer is not correct! The Tokio tasks are very lightweight, so they won't use much memory and they will be scheduled on up to N OS threads (at most 256 in this unit test), so there is no need to bounding the number of the tasks.

@martin-augment
Copy link
Owner Author

1. Critical: Infinite loop in shutdown test (tokio/tests/fs_uring_symlink.rs:57)

The loop runs to usize::MAX, which on 64-bit systems is 18,446,744,073,709,551,615. This test will never complete normally.

Recommendation: Use a reasonable constant like other tests do (e.g., 100 or 1000)

value:incorrect-but-reasonable; category:bug; feedback:The Claude AI reviewer is not correct! The huge amount of tasks are scheduled in the Tokio runtime but at the same time an OS thread is scheduled to execute after 300ms to shutdown the runtime, so only a few hundreds/thousands tasks will be actually ran.

@martin-augment
Copy link
Owner Author

2. Test quality: Unnecessary spawning in shutdown test (tokio/tests/fs_uring_symlink.rs:60)

The shutdown test spawns individual tasks for each symlink operation, but never awaits or tracks them. This creates tasks that are abandoned, making the test behavior less predictable.

Recommendation: Consider using a pattern more similar to fs_uring_read.rs:50-75 where futures are stored and managed.

value:incorrect-but-reasonable; category:bug; feedback:The Claude AI reviewer is not correct! The purpose of the test is to give a lot of work to the Tokio runtime (to spawn many tasks) and to shut it down in a separate OS thread. It tests that the runtime could be shut down even under a high load.

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