Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ httparse = "1.0"
httpdate = "1.0"
once_cell = "1.5.2"

[target.'cfg(target_os = "linux")'.dev-dependencies]
libc = "0.2"

[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }

Expand Down Expand Up @@ -102,5 +105,9 @@ path = "named-pipe-multi-client.rs"
name = "dump"
path = "dump.rs"

[[example]]
name = "prewarm-fd-table"
path = "prewarm-fd-table.rs"

[lints]
workspace = true
80 changes: 80 additions & 0 deletions examples/prewarm-fd-table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! Demonstrates pre-warming the Linux file descriptor table to avoid latency
//! spikes caused by file descriptor table growth in multi-threaded processes.
//!
//! On Linux, the kernel's FD table is grown lazily and protected by RCU
//! synchronization. In multi-threaded processes, when a syscall like `socket()`
//! triggers a table resize, the calling thread blocks until all RCU readers
//! quiesce. This can cause stalls of tens of milliseconds on tokio worker threads,
//! blocking the entire event loop (not just one task).
//!
//! The workaround is to force the kernel to expand the FD table once per process
//! (before any runtime starts), by duplicating an FD to a high slot and then
//! closing it. The kernel never shrinks the FD table during a process's lifetime,
//! so the capacity persists.
//!
//! This is most relevant for services that open many connections concurrently
//! (e.g. HTTP servers, connection pools). The pre-warm target should be at least
//! your expected peak FD count.
//!
//! See: <https://github.com/tokio-rs/tokio/issues/7970>
//!
//! Usage:
//!
//! cargo run --example prewarm-fd-table

#![warn(rust_2018_idioms)]

#[cfg(target_os = "linux")]
fn prewarm_fd_table(target: u32) -> std::io::Result<()> {
use std::os::unix::io::{FromRawFd, OwnedFd};

// Open /dev/null to get a base FD.
let dev_null = std::fs::File::open("/dev/null")?;

// Use F_DUPFD_CLOEXEC to duplicate to a slot >= target. This forces the
// kernel to expand the FD table in a single syscall, without clobbering
// existing FDs. CLOEXEC prevents leaking to child processes.
let raw = unsafe {
libc::fcntl(
std::os::unix::io::AsRawFd::as_raw_fd(&dev_null),
libc::F_DUPFD_CLOEXEC,
target,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because libc::fcntl is varargs, passing target as a u32 can be ABI-incorrect on some platforms; consider casting to the exact expected C type for F_DUPFD_CLOEXEC (typically libc::c_int) to avoid UB in this unsafe call.

Severity: medium

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-to-have; category:bug; feedback: The Augment AI reviewer is correct! FFI with libc uses c_int, which is i32 in Rust. Using other integer types may work or not. It is better to use the same types as the libc's APIs.

)
Comment on lines +29 to +42
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 target parameter is a u32, but fcntl's third argument for F_DUPFD_CLOEXEC is a c_int (i32 on Linux). Passing a u32 value that is larger than i32::MAX will be interpreted as a negative number, causing fcntl to fail. It's safer to validate the input and use the correct type.

    use std::convert::TryInto;
    use std::os::unix::io::{FromRawFd, OwnedFd};

    let target = target.try_into().map_err(|e| {
        std::io::Error::new(std::io::ErrorKind::InvalidInput, e)
    })?;

    // Open /dev/null to get a base FD.
    let dev_null = std::fs::File::open("/dev/null")?;

    // Use F_DUPFD_CLOEXEC to duplicate to a slot >= target. This forces the
    // kernel to expand the FD table in a single syscall, without clobbering
    // existing FDs. CLOEXEC prevents leaking to child processes.
    let raw = unsafe {
        libc::fcntl(
            std::os::unix::io::AsRawFd::as_raw_fd(&dev_null),
            libc::F_DUPFD_CLOEXEC,
            target,
        )

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! FFI with libc uses c_int, which is i32 in Rust. Using other integer types may work or not. It is better to use the same types as the libc's APIs.

};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong type passed to variadic FFI fcntl call

Low Severity

The target parameter is typed u32 but is passed directly as a variadic argument to libc::fcntl, which expects c_int (i32) for F_DUPFD_CLOEXEC. Passing a mismatched type to a C variadic function is undefined behavior per the C standard. While harmless for the hardcoded value of 10,000, this example is designed to be copied by users who might supply larger values — any u32 above i32::MAX would be UB. The parameter type (and FD_TARGET const) would be more correct as libc::c_int or i32.

Fix in Cursor Fix in Web

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 Bugbot AI reviewer is correct! FFI with libc uses c_int, which is i32 in Rust. Using other integer types may work or not. It is better to use the same types as the libc's APIs.

if raw < 0 {
return Err(std::io::Error::last_os_error());
}

// Close both FDs. The table capacity persists.
let _owned = unsafe { OwnedFd::from_raw_fd(raw) };
drop(dev_null);

Ok(())
}

#[cfg(not(target_os = "linux"))]
fn prewarm_fd_table(_target: u32) -> std::io::Result<()> {
// FD table pre-warming is Linux-specific.
Ok(())
}

fn main() {
const FD_TARGET: u32 = 10_000;

println!("Pre-warming FD table to {FD_TARGET} entries...");
if let Err(e) = prewarm_fd_table(FD_TARGET) {
eprintln!("Warning: failed to pre-warm FD table: {e}");
} else {
println!("FD table pre-warmed successfully.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On non-Linux targets prewarm_fd_table is a no-op that returns Ok(()), so this “pre-warmed successfully” message can be misleading when the example is run on macOS/Windows.

Severity: low

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-to-have; category:bug; feedback: The Augment AI reviewer is correct! The whole example does not make sense for non-Linux runs. It would be better to return early in main() with a message printed on the stdout that the example does nothing on non-Linux. Currently it prints success messages even when it did nothing and this may confuse the users.

}
Comment on lines +65 to +69
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 | 🟡 Minor

Non-Linux path reports a false success message.

On non-Linux, prewarm_fd_table is a no-op but this branch still prints FD table pre-warmed successfully. which is misleading.

Suggested adjustment
 fn main() {
     const FD_TARGET: u32 = 10_000;

     println!("Pre-warming FD table to {FD_TARGET} entries...");
-    if let Err(e) = prewarm_fd_table(FD_TARGET) {
-        eprintln!("Warning: failed to pre-warm FD table: {e}");
-    } else {
-        println!("FD table pre-warmed successfully.");
-    }
+    #[cfg(target_os = "linux")]
+    {
+        if let Err(e) = prewarm_fd_table(FD_TARGET) {
+            eprintln!("Warning: failed to pre-warm FD table: {e}");
+        } else {
+            println!("FD table pre-warmed successfully.");
+        }
+    }
+    #[cfg(not(target_os = "linux"))]
+    {
+        let _ = prewarm_fd_table(FD_TARGET);
+        println!("Skipping FD table pre-warm (Linux-only workaround).");
+    }

     // Build the runtime *after* pre-warming.
📝 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
if let Err(e) = prewarm_fd_table(FD_TARGET) {
eprintln!("Warning: failed to pre-warm FD table: {e}");
} else {
println!("FD table pre-warmed successfully.");
}
#[cfg(target_os = "linux")]
{
if let Err(e) = prewarm_fd_table(FD_TARGET) {
eprintln!("Warning: failed to pre-warm FD table: {e}");
} else {
println!("FD table pre-warmed successfully.");
}
}
#[cfg(not(target_os = "linux"))]
{
let _ = prewarm_fd_table(FD_TARGET);
println!("Skipping FD table pre-warm (Linux-only workaround).");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/prewarm-fd-table.rs` around lines 65 - 69, The code prints "FD table
pre-warmed successfully." even on non-Linux where prewarm_fd_table(FD_TARGET) is
a no-op; change the logic so the success message is only emitted when the
pre-warm actually ran (e.g., guard the println! with cfg!(target_os = "linux")
or adjust prewarm_fd_table to return a boolean/enum indicating it performed work
and print only when that indicates success). Update the branch around the call
to prewarm_fd_table(FD_TARGET) (and the associated success println!) to rely on
that condition so non-Linux platforms no longer show a false success.

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 whole example does not make sense for non-Linux runs. It would be better to return early in main() with a message printed on the stdout that the example does nothing on non-Linux. Currently it prints success messages even when it did nothing and this may confuse the users.


// Build the runtime *after* pre-warming.
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();

rt.block_on(async {
println!("Runtime started. Worker threads will not stall on FD table growth up to {FD_TARGET} FDs.");
});
}
3 changes: 2 additions & 1 deletion spellcheck.dic
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
312
313
&
+
<
Expand Down Expand Up @@ -209,6 +209,7 @@ POSIX
proxied
qos
RAII
RCU
reallocations
recv's
refactors
Expand Down
8 changes: 8 additions & 0 deletions tokio/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,13 @@
//! When a task is woken from a thread that is not a worker thread, then the
//! task is placed in the global queue.
//!
//! # Performance tuning
//!
//! ## File descriptor table pre-warming
//!
//! On Linux, file descriptor table growth can stall worker threads. See the
//! [`prewarm-fd-table`] example.
//!
//! [`poll`]: std::future::Future::poll
//! [`wake`]: std::task::Waker::wake
//! [`yield_now`]: crate::task::yield_now
Expand All @@ -385,6 +392,7 @@
//! [the lifo slot optimization]: crate::runtime::Builder::disable_lifo_slot
//! [coop budget]: crate::task::coop#cooperative-scheduling
//! [`worker_mean_poll_time`]: crate::runtime::RuntimeMetrics::worker_mean_poll_time
//! [`prewarm-fd-table`]: https://github.com/tokio-rs/tokio/blob/master/examples/prewarm-fd-table.rs

// At the top due to macros
#[cfg(test)]
Expand Down
Loading