mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-28 00:00:11 +02:00
examples: add FD table pre-warming example (#7978)
This commit is contained in:
@@ -24,6 +24,9 @@ httparse = "1.0"
|
|||||||
httpdate = "1.0"
|
httpdate = "1.0"
|
||||||
once_cell = "1.5.2"
|
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]
|
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
|
||||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }
|
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }
|
||||||
|
|
||||||
@@ -102,5 +105,9 @@ path = "named-pipe-multi-client.rs"
|
|||||||
name = "dump"
|
name = "dump"
|
||||||
path = "dump.rs"
|
path = "dump.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "prewarm-fd-table"
|
||||||
|
path = "prewarm-fd-table.rs"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
//! 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, and must not exceed `RLIMIT_NOFILE`.
|
||||||
|
//!
|
||||||
|
//! See: <https://github.com/tokio-rs/tokio/issues/7970>
|
||||||
|
//!
|
||||||
|
//! Usage:
|
||||||
|
//!
|
||||||
|
//! cargo run --example prewarm-fd-table
|
||||||
|
|
||||||
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
|
/// Pre-warms the FD table using `fcntl(F_DUPFD_CLOEXEC)` to duplicate an FD
|
||||||
|
/// into a high slot, expanding the table in a single syscall. `F_DUPFD_CLOEXEC`
|
||||||
|
/// allocates the lowest available FD >= `target`, so it never clobbers an
|
||||||
|
/// existing FD.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn prewarm_fd_table(target: i32) -> std::io::Result<()> {
|
||||||
|
use std::os::unix::io::{FromRawFd, OwnedFd};
|
||||||
|
|
||||||
|
let dev_null = std::fs::File::open("/dev/null")?;
|
||||||
|
let raw = unsafe {
|
||||||
|
libc::fcntl(
|
||||||
|
std::os::unix::io::AsRawFd::as_raw_fd(&dev_null),
|
||||||
|
libc::F_DUPFD_CLOEXEC,
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fully safe alternative using only stdlib. Requires O(n) syscalls instead of
|
||||||
|
/// one, but avoids `unsafe` entirely.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn prewarm_fd_table_safe(target: i32) -> std::io::Result<()> {
|
||||||
|
let f = std::fs::File::open("/dev/null")?;
|
||||||
|
let _fds: Vec<_> = (0..target)
|
||||||
|
.map(|_| f.try_clone())
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
const FD_TARGET: i32 = 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the runtime *after* pre-warming.
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
rt.block_on(async {});
|
||||||
|
}
|
||||||
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
312
|
313
|
||||||
&
|
&
|
||||||
+
|
+
|
||||||
<
|
<
|
||||||
@@ -209,6 +209,7 @@ POSIX
|
|||||||
proxied
|
proxied
|
||||||
qos
|
qos
|
||||||
RAII
|
RAII
|
||||||
|
RCU
|
||||||
reallocations
|
reallocations
|
||||||
recv's
|
recv's
|
||||||
refactors
|
refactors
|
||||||
|
|||||||
@@ -373,6 +373,13 @@
|
|||||||
//! When a task is woken from a thread that is not a worker thread, then the
|
//! When a task is woken from a thread that is not a worker thread, then the
|
||||||
//! task is placed in the global queue.
|
//! 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
|
//! [`poll`]: std::future::Future::poll
|
||||||
//! [`wake`]: std::task::Waker::wake
|
//! [`wake`]: std::task::Waker::wake
|
||||||
//! [`yield_now`]: crate::task::yield_now
|
//! [`yield_now`]: crate::task::yield_now
|
||||||
@@ -385,6 +392,7 @@
|
|||||||
//! [the lifo slot optimization]: crate::runtime::Builder::disable_lifo_slot
|
//! [the lifo slot optimization]: crate::runtime::Builder::disable_lifo_slot
|
||||||
//! [coop budget]: crate::task::coop#cooperative-scheduling
|
//! [coop budget]: crate::task::coop#cooperative-scheduling
|
||||||
//! [`worker_mean_poll_time`]: crate::runtime::RuntimeMetrics::worker_mean_poll_time
|
//! [`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
|
// At the top due to macros
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user