|
6 | 6 | // spell-checker:ignore NPROCESSORS SCHED getaffinity getcpu getscheduler sched sysconf |
7 | 7 |
|
8 | 8 | use clap::{Arg, ArgAction, Command}; |
| 9 | +use std::env; |
9 | 10 | use std::io::{Write, stdout}; |
10 | | -use std::{env, thread}; |
11 | 11 | use uucore::error::{UResult, USimpleError}; |
12 | 12 | use uucore::format_usage; |
13 | 13 | use uucore::translate; |
@@ -90,36 +90,44 @@ pub fn uu_app() -> Command { |
90 | 90 | ) |
91 | 91 | } |
92 | 92 |
|
93 | | -#[cfg(unix)] |
94 | 93 | 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() |
103 | 101 | } |
104 | 102 |
|
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) |
109 | 117 | } |
110 | 118 |
|
111 | | -/// In some cases, [`thread::available_parallelism`]() may return an Err |
112 | | -/// In this case, we will return 1 (like GNU) |
113 | 119 | 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); |
114 | 124 | // ignore quota under some schedulers |
115 | 125 | #[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)), |
122 | 130 | } |
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) |
125 | 133 | } |
0 commit comments