diff --git a/Cargo.lock b/Cargo.lock index 7460cfc0513..7f89bde6ec6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9088,6 +9088,7 @@ dependencies = [ "fdlimit", "futures", "jsonrpsee", + "libc", "rayon", "reth-basic-payload-builder", "reth-chain-state", diff --git a/crates/cli/commands/src/node.rs b/crates/cli/commands/src/node.rs index cba857a3a84..b8b95328303 100644 --- a/crates/cli/commands/src/node.rs +++ b/crates/cli/commands/src/node.rs @@ -66,6 +66,13 @@ pub struct NodeCommand = NodeCommand::parse_from(["reth"]); + assert_eq!(cmd.fdlimit, 0); + } + + #[test] + fn parse_fdlimit_custom() { + let cmd: NodeCommand = + NodeCommand::parse_from(["reth", "--fdlimit", "10000"]); + assert_eq!(cmd.fdlimit, 10000); + } + + #[test] + fn parse_fdlimit_zero() { + let cmd: NodeCommand = + NodeCommand::parse_from(["reth", "--fdlimit", "0"]); + assert_eq!(cmd.fdlimit, 0); + } } diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index df89dcfdf55..05d2f091d72 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -78,6 +78,7 @@ aquamarine.workspace = true eyre.workspace = true jsonrpsee.workspace = true fdlimit.workspace = true +libc = "0.2" rayon.workspace = true serde_json.workspace = true diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 7c6f5bfa7b7..776e26572dd 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -201,8 +201,8 @@ impl LaunchContext { } /// Convenience function to [`Self::configure_globals`] - pub fn with_configured_globals(self, reserved_cpu_cores: usize) -> Self { - self.configure_globals(reserved_cpu_cores); + pub fn with_configured_globals(self, reserved_cpu_cores: usize, fdlimit: u64) -> Self { + self.configure_globals(reserved_cpu_cores, fdlimit); self } @@ -212,15 +212,51 @@ impl LaunchContext { /// - Configuring the global rayon thread pool with available parallelism. Honoring /// engine.reserved-cpu-cores to reserve given number of cores for O while using at least 1 /// core for the rayon thread pool - pub fn configure_globals(&self, reserved_cpu_cores: usize) { + pub fn configure_globals(&self, reserved_cpu_cores: usize, fdlimit: u64) { // Raise the fd limit of the process. // Does not do anything on windows. - match fdlimit::raise_fd_limit() { - Ok(fdlimit::Outcome::LimitRaised { from, to }) => { - debug!(from, to, "Raised file descriptor limit"); + if fdlimit == 0 { + // Use system default (raise to maximum) + match fdlimit::raise_fd_limit() { + Ok(fdlimit::Outcome::LimitRaised { from, to }) => { + debug!(from, to, "Raised file descriptor limit"); + } + Ok(fdlimit::Outcome::Unsupported) => {} + Err(err) => warn!(%err, "Failed to raise file descriptor limit"), + } + } else { + // Set to specific limit + #[cfg(unix)] + { + let result = unsafe { + let mut rlimit = libc::rlimit { rlim_cur: 0, rlim_max: 0 }; + if libc::getrlimit(libc::RLIMIT_NOFILE, &raw mut rlimit) == 0 { + let old_limit = rlimit.rlim_cur; + rlimit.rlim_cur = fdlimit.min(rlimit.rlim_max) as libc::rlim_t; + if libc::setrlimit(libc::RLIMIT_NOFILE, &raw const rlimit) == 0 { + if rlimit.rlim_cur != old_limit { + debug!( + from = old_limit, + to = rlimit.rlim_cur, + "Set file descriptor limit" + ); + } + Ok(()) + } else { + Err(std::io::Error::last_os_error()) + } + } else { + Err(std::io::Error::last_os_error()) + } + }; + if let Err(err) = result { + warn!(%err, limit = fdlimit, "Failed to set file descriptor limit"); + } + } + #[cfg(not(unix))] + { + warn!("File descriptor limit setting is not supported on this platform"); } - Ok(fdlimit::Outcome::Unsupported) => {} - Err(err) => warn!(%err, "Failed to raise file descriptor limit"), } // Reserving the given number of CPU cores for the rest of OS. @@ -261,8 +297,8 @@ impl LaunchContextWith { /// /// - Raising the file descriptor limit /// - Configuring the global rayon thread pool - pub fn configure_globals(&self, reserved_cpu_cores: u64) { - self.inner.configure_globals(reserved_cpu_cores.try_into().unwrap()); + pub fn configure_globals(&self, reserved_cpu_cores: u64, fdlimit: u64) { + self.inner.configure_globals(reserved_cpu_cores.try_into().unwrap(), fdlimit); } /// Returns the data directory. diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index c181586050d..f41df02709d 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -87,8 +87,9 @@ impl EngineNodeLauncher { let NodeHooks { on_component_initialized, on_node_started, .. } = hooks; // setup the launch context + let fdlimit = config.fdlimit; let ctx = ctx - .with_configured_globals(engine_tree_config.reserved_cpu_cores()) + .with_configured_globals(engine_tree_config.reserved_cpu_cores(), fdlimit) // load the toml config .with_loaded_toml_config(config)? // add resolved peers diff --git a/crates/node/core/src/node_config.rs b/crates/node/core/src/node_config.rs index 1d5b1700cbe..f73c7a7a0ea 100644 --- a/crates/node/core/src/node_config.rs +++ b/crates/node/core/src/node_config.rs @@ -118,6 +118,12 @@ pub struct NodeConfig { /// - `IPC_PATH`: default + `instance` pub instance: Option, + /// File descriptor limit. + /// + /// Default: 0 (system default). When set to 0, uses the system's default file descriptor + /// limit. When set to a positive value, attempts to raise the limit to that value. + pub fdlimit: u64, + /// All networking related arguments pub network: NetworkArgs, @@ -169,6 +175,7 @@ impl NodeConfig { chain, metrics: MetricArgs::default(), instance: None, + fdlimit: 0, network: NetworkArgs::default(), rpc: RpcServerArgs::default(), txpool: TxPoolArgs::default(), @@ -244,6 +251,7 @@ impl NodeConfig { config, metrics, instance, + fdlimit, network, rpc, txpool, @@ -263,6 +271,7 @@ impl NodeConfig { chain: chain.into(), metrics, instance, + fdlimit, network, rpc, txpool, @@ -533,6 +542,7 @@ impl NodeConfig { config: self.config, metrics: self.metrics, instance: self.instance, + fdlimit: self.fdlimit, network: self.network, rpc: self.rpc, txpool: self.txpool, @@ -573,6 +583,7 @@ impl Clone for NodeConfig { config: self.config.clone(), metrics: self.metrics.clone(), instance: self.instance, + fdlimit: self.fdlimit, network: self.network.clone(), rpc: self.rpc.clone(), txpool: self.txpool.clone(), diff --git a/docs/vocs/docs/pages/cli/op-reth/node.mdx b/docs/vocs/docs/pages/cli/op-reth/node.mdx index 2a6450d344c..db224f02fc4 100644 --- a/docs/vocs/docs/pages/cli/op-reth/node.mdx +++ b/docs/vocs/docs/pages/cli/op-reth/node.mdx @@ -35,6 +35,13 @@ Options: Mutually exclusive with `--instance`. + --fdlimit + Raise the open file descriptor resource limit. + + Default: 0 (system default). When set to 0, uses the system's default file descriptor limit. When set to a positive value, attempts to raise the limit to that value. + + [default: 0] + -h, --help Print help (see a summary with '-h') diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 6542d0c5bf8..c568511170d 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -35,6 +35,13 @@ Options: Mutually exclusive with `--instance`. + --fdlimit + Raise the open file descriptor resource limit. + + Default: 0 (system default). When set to 0, uses the system's default file descriptor limit. When set to a positive value, attempts to raise the limit to that value. + + [default: 0] + -h, --help Print help (see a summary with '-h')