use rustix::process::{Resource, Rlimit, getrlimit, setrlimit};
use std::fs::File;
pub(crate) struct NofileLimit(Rlimit);
impl NofileLimit {
pub(crate) fn lower_to(soft: u64) -> Self {
let saved = getrlimit(Resource::Nofile);
let guard = Self(saved);
setrlimit(
Resource::Nofile,
Rlimit {
current: Some(saved.current.map_or(soft, |current| soft.min(current))),
maximum: saved.maximum,
},
)
.expect("lower the NOFILE soft limit");
guard
}
pub(crate) fn set_to(soft: u64) -> Self {
let saved = getrlimit(Resource::Nofile);
let guard = Self(saved);
setrlimit(
Resource::Nofile,
Rlimit {
current: Some(saved.maximum.map_or(soft, |hard| soft.min(hard))),
maximum: saved.maximum,
},
)
.expect("set the NOFILE soft limit");
guard
}
}
impl Drop for NofileLimit {
fn drop(&mut self) {
let _ = setrlimit(Resource::Nofile, self.0);
}
}
pub(crate) struct HeldDescriptors {
_held: Vec<File>,
_limit: NofileLimit,
}
impl HeldDescriptors {
pub(crate) fn leaving(free: usize) -> Self {
const MARGIN: usize = 8;
for soft in [128u64, 512, 2048] {
let limit = NofileLimit::set_to(soft);
let mut held = Vec::new();
while let Ok(file) = File::open("/dev/null") {
held.push(file);
}
if held.len() >= free + MARGIN {
held.truncate(held.len() - free);
return Self {
_held: held,
_limit: limit,
};
}
}
panic!("no open-file ceiling left room to hold {free} descriptors free");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore = "changes the process-wide open-file limit; needs --test-threads=1"]
fn set_to_raises_toward_the_hard_limit_and_restores_on_drop() {
let saved = getrlimit(Resource::Nofile);
let Some(soft) = saved.current else {
return;
};
let target = soft + 1;
if saved.maximum.is_some_and(|hard| target > hard) {
return;
}
{
let _guard = NofileLimit::set_to(target);
assert_eq!(getrlimit(Resource::Nofile).current, Some(target));
}
assert_eq!(getrlimit(Resource::Nofile).current, Some(soft));
}
}