tuwunel_core/utils/sys/limits.rs
1//! Process resource-limit utilities.
2//!
3//! The helpers query soft and hard limits and raise selected soft limits when
4//! supported. Platform-specific implementations provide neutral fallbacks when
5//! an interface is unavailable.
6
7#[cfg(unix)]
8use nix::sys::resource::{Resource, getrlimit};
9#[cfg(unix)]
10use nix::unistd::{SysconfVar, sysconf};
11
12use crate::Result;
13#[cfg(unix)]
14use crate::{apply, debug, utils::math::ExpectInto};
15
16#[cfg(unix)]
17/// Raises the soft file descriptor limit to the current hard limit.
18///
19/// RocksDB and concurrent federation connections can exceed the common soft
20/// limit of 1,024 during startup. Systemd commonly provides a hard limit of
21/// 524,288.
22///
23/// * <https://www.freedesktop.org/software/systemd/man/systemd.exec.html#id-1.12.2.1.17.6>
24/// * <https://github.com/systemd/systemd/commit/0abf94923b4a95a7d89bc526efc84e7ca2b71741>
25pub fn maximize_fd_limit() -> Result {
26 use nix::sys::resource::setrlimit;
27
28 let (soft_limit, hard_limit) = max_file_descriptors()?;
29 if soft_limit < hard_limit {
30 let new_limit = hard_limit.try_into()?;
31 setrlimit(Resource::RLIMIT_NOFILE, new_limit, new_limit)?;
32 assert_eq!((hard_limit, hard_limit), max_file_descriptors()?, "getrlimit != setrlimit");
33 debug!(to = hard_limit, from = soft_limit, "Raised RLIMIT_NOFILE");
34 }
35
36 Ok(())
37}
38
39#[cfg(not(unix))]
40/// Performs no file-descriptor limit adjustment on unsupported platforms.
41pub fn maximize_fd_limit() -> Result { Ok(()) }
42
43#[cfg(all(unix, not(target_os = "macos")))]
44/// Raises the soft thread limit to the current hard limit.
45///
46/// Some distributions default to about 1,024 threads, which can constrain hosts
47/// with 32 or more cores. Thread limits are otherwise reached less often than
48/// file descriptor limits.
49pub fn maximize_thread_limit() -> Result {
50 use nix::sys::resource::setrlimit;
51
52 let (soft_limit, hard_limit) = max_threads()?;
53 if soft_limit < hard_limit {
54 let new_limit = hard_limit.try_into()?;
55 setrlimit(Resource::RLIMIT_NPROC, new_limit, new_limit)?;
56 assert_eq!((hard_limit, hard_limit), max_threads()?, "getrlimit != setrlimit");
57 debug!(to = hard_limit, from = soft_limit, "Raised RLIMIT_NPROC");
58 }
59
60 Ok(())
61}
62
63#[cfg(any(not(unix), target_os = "macos"))]
64/// Performs no thread limit adjustment on platforms where nix does not expose
65/// `RLIMIT_NPROC`, notably macOS.
66pub fn maximize_thread_limit() -> Result { Ok(()) }
67
68/// Returns the soft and hard file-descriptor limits.
69///
70/// The tuple is ordered as the current soft limit followed by the maximum hard
71/// limit. Values come from `RLIMIT_NOFILE`.
72///
73/// # Panics
74///
75/// Panics when either platform limit cannot be represented as `usize`.
76#[cfg(unix)]
77#[inline]
78pub fn max_file_descriptors() -> Result<(usize, usize)> {
79 getrlimit(Resource::RLIMIT_NOFILE)
80 .map(apply!(2, ExpectInto::expect_into))
81 .map_err(Into::into)
82}
83
84/// Returns sentinel file-descriptor limits on unsupported platforms.
85///
86/// Both tuple elements are `usize::MAX`, representing no known finite limit.
87/// No operating-system query is performed.
88#[cfg(not(unix))]
89#[inline]
90pub fn max_file_descriptors() -> Result<(usize, usize)> { Ok((usize::MAX, usize::MAX)) }
91
92/// Returns the soft and hard process stack-size limits.
93///
94/// The tuple is ordered as the current soft limit followed by the maximum hard
95/// limit. Values come from `RLIMIT_STACK`.
96///
97/// # Panics
98///
99/// Panics when either platform limit cannot be represented as `usize`.
100#[cfg(unix)]
101#[inline]
102pub fn max_stack_size() -> Result<(usize, usize)> {
103 getrlimit(Resource::RLIMIT_STACK)
104 .map(apply!(2, ExpectInto::expect_into))
105 .map_err(Into::into)
106}
107
108/// Returns sentinel stack-size limits on unsupported platforms.
109///
110/// Both tuple elements are `usize::MAX`, representing no known finite limit.
111/// No operating-system query is performed.
112#[cfg(not(unix))]
113#[inline]
114pub fn max_stack_size() -> Result<(usize, usize)> { Ok((usize::MAX, usize::MAX)) }
115
116/// Returns the soft and hard locked-memory limits.
117///
118/// The tuple is ordered as the current soft limit followed by the maximum hard
119/// limit. Values come from `RLIMIT_MEMLOCK`.
120///
121/// # Panics
122///
123/// Panics when either platform limit cannot be represented as `usize`.
124#[cfg(all(unix, not(target_os = "macos")))]
125#[inline]
126pub fn max_memory_locked() -> Result<(usize, usize)> {
127 getrlimit(Resource::RLIMIT_MEMLOCK)
128 .map(apply!(2, ExpectInto::expect_into))
129 .map_err(Into::into)
130}
131
132/// Returns sentinel locked-memory limits on unsupported platforms.
133///
134/// Both tuple elements are zero, the module's unsupported-platform sentinel.
135/// No operating-system query is performed.
136#[cfg(any(not(unix), target_os = "macos"))]
137#[inline]
138pub fn max_memory_locked() -> Result<(usize, usize)> { Ok((usize::MIN, usize::MIN)) }
139
140/// Returns the soft and hard per-user process-count limits.
141///
142/// The tuple is ordered as the current soft limit followed by the maximum hard
143/// limit. Values come from `RLIMIT_NPROC`; on Linux, it counts extant threads
144/// for the caller's real user ID.
145///
146/// # Panics
147///
148/// Panics when either platform limit cannot be represented as `usize`.
149#[cfg(all(unix, not(target_os = "macos")))]
150#[inline]
151pub fn max_threads() -> Result<(usize, usize)> {
152 getrlimit(Resource::RLIMIT_NPROC)
153 .map(apply!(2, ExpectInto::expect_into))
154 .map_err(Into::into)
155}
156
157/// Returns sentinel thread limits on unsupported platforms.
158///
159/// Both tuple elements are `usize::MAX`, representing no known finite limit.
160/// No operating-system query is performed.
161#[cfg(any(not(unix), target_os = "macos"))]
162#[inline]
163pub fn max_threads() -> Result<(usize, usize)> { Ok((usize::MAX, usize::MAX)) }
164
165#[cfg(unix)]
166/// Get the system's page size in bytes.
167#[inline]
168pub fn page_size() -> Result<usize> {
169 sysconf(SysconfVar::PAGE_SIZE)?
170 .unwrap_or(-1)
171 .try_into()
172 .map_err(Into::into)
173}
174
175#[cfg(not(unix))]
176/// Get the system's page size in bytes.
177#[inline]
178pub fn page_size() -> Result<usize> { Ok(4096) }