Skip to content

Commit 073b882

Browse files
authored
nproc: manually parse cgroups2 quota (#12871)
1 parent 4eb0229 commit 073b882

1 file changed

Lines changed: 32 additions & 24 deletions

File tree

src/uu/nproc/src/nproc.rs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
// spell-checker:ignore NPROCESSORS SCHED getaffinity getcpu getscheduler sched sysconf
77

88
use clap::{Arg, ArgAction, Command};
9+
use std::env;
910
use std::io::{Write, stdout};
10-
use std::{env, thread};
1111
use uucore::error::{UResult, USimpleError};
1212
use uucore::format_usage;
1313
use uucore::translate;
@@ -90,36 +90,44 @@ pub fn uu_app() -> Command {
9090
)
9191
}
9292

93-
#[cfg(unix)]
9493
fn num_cpus_all() -> usize {
95-
// In some situation, /proc and /sys are not mounted, and sysconf returns 1.
96-
// However, we want to guarantee that `nproc --all` >= `nproc`.
97-
// rustix::thread::sched_getaffinity is linux only
98-
unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) }
99-
.try_into()
100-
.ok()
101-
.filter(|&n: &isize| n > 1)
102-
.map_or_else(available_parallelism, |n| n as usize)
94+
// sysconf returns 2 if /proc and /sys are masked, and sched_getaffinity syscall was blocked by strace
95+
// when SMT is enabled. So fallback to available_parallelism at here is not useful
96+
#[cfg(unix)]
97+
return unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) }.max(1) as usize;
98+
// not sure what we can do for non-unix...
99+
#[cfg(not(unix))]
100+
available_parallelism()
103101
}
104102

105-
// Other platforms (e.g., windows), available_parallelism() directly.
106-
#[cfg(not(unix))]
107-
fn num_cpus_all() -> usize {
108-
available_parallelism()
103+
// We cannot use std::thread::available_parallelism to mimic GNU's rounding...
104+
#[cfg(any(target_os = "linux", target_os = "android"))]
105+
fn cgroups2_quota() -> Option<usize> {
106+
use std::fs::read_to_string;
107+
let cgroups = read_to_string("/proc/self/cgroup").ok()?;
108+
let path = cgroups.lines().next()?.split(':').nth(2)?;
109+
let pair = read_to_string(format!("/sys/fs/cgroup{path}/cpu.max")).ok()?;
110+
let mut pair = pair.split_whitespace();
111+
// map the string "max" to None as we unwrap_or(usize::MAX) later
112+
let quota = pair.next()?.parse::<usize>().ok()?;
113+
let period = pair.next()?.parse::<usize>().ok()?;
114+
debug_assert!(period > 0, "kernel should validate it");
115+
// mimic GNU's rounding
116+
Some(quota.saturating_add(period / 2) / period)
109117
}
110118

111-
/// In some cases, [`thread::available_parallelism`]() may return an Err
112-
/// In this case, we will return 1 (like GNU)
113119
fn available_parallelism() -> usize {
120+
// return all cores if sched_getaffinity syscall failed as same as GNU
121+
#[cfg(any(target_os = "linux", target_os = "android"))]
122+
let affinity = rustix::thread::sched_getaffinity(None)
123+
.map_or_else(|_| num_cpus_all(), |s| s.count() as usize);
114124
// ignore quota under some schedulers
115125
#[cfg(any(target_os = "linux", target_os = "android"))]
116-
if matches!(
117-
unsafe { libc::sched_getscheduler(0) },
118-
libc::SCHED_FIFO | libc::SCHED_RR | libc::SCHED_DEADLINE
119-
) {
120-
// with affinity mask
121-
return rustix::thread::sched_getcpu();
126+
match unsafe { libc::sched_getscheduler(0) } {
127+
libc::SCHED_FIFO | libc::SCHED_RR | libc::SCHED_DEADLINE => affinity,
128+
// GNU has no quota if /proc is masked
129+
_ => affinity.min(cgroups2_quota().unwrap_or(usize::MAX)),
122130
}
123-
124-
thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get)
131+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
132+
std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get)
125133
}

0 commit comments

Comments
 (0)