From 18b1baa3c1f1ca8970d995c1db7d50113d49a5c0 Mon Sep 17 00:00:00 2001 From: Ivan Mironov Date: Wed, 27 Oct 2021 06:07:37 +0500 Subject: [PATCH] Add function for changing thread's nice value Linux only. --- Cargo.lock | 35 +++++++++++++++ perf/Cargo.toml | 5 +++ perf/src/lib.rs | 1 + perf/src/thread.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 perf/src/thread.rs diff --git a/Cargo.lock b/Cargo.lock index 18d5f1fd9..bfa8cba86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,6 +565,17 @@ dependencies = [ "serde", ] +[[package]] +name = "caps" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61bf7211aad104ce2769ec05efcdfabf85ee84ac92461d142f22cf8badd0e54c" +dependencies = [ + "errno", + "libc", + "thiserror", +] + [[package]] name = "cargo-platform" version = "0.1.2" @@ -1270,6 +1281,27 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "etcd-client" version = "0.7.2" @@ -5268,12 +5300,15 @@ name = "solana-perf" version = "1.9.0" dependencies = [ "bincode", + "caps", "curve25519-dalek 3.2.0", "dlopen", "dlopen_derive", "lazy_static", + "libc", "log 0.4.14", "matches", + "nix", "rand 0.7.3", "rayon", "serde", diff --git a/perf/Cargo.toml b/perf/Cargo.toml index 25dd0b4d5..2429e65a4 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -25,6 +25,11 @@ solana-sdk = { path = "../sdk", version = "=1.9.0" } solana-vote-program = { path = "../programs/vote", version = "=1.9.0" } solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.9.0" } +[target."cfg(target_os = \"linux\")".dependencies] +caps = "0.5.3" +libc = "0.2.105" +nix = "0.23.0" + [lib] name = "solana_perf" diff --git a/perf/src/lib.rs b/perf/src/lib.rs index b98490d30..e5352a6fd 100644 --- a/perf/src/lib.rs +++ b/perf/src/lib.rs @@ -6,6 +6,7 @@ pub mod recycler; pub mod recycler_cache; pub mod sigverify; pub mod test_tx; +pub mod thread; #[macro_use] extern crate lazy_static; diff --git a/perf/src/thread.rs b/perf/src/thread.rs new file mode 100644 index 000000000..4f826ee6d --- /dev/null +++ b/perf/src/thread.rs @@ -0,0 +1,105 @@ +/// Wrapper for `nice(3)`. +#[cfg(target_os = "linux")] +fn nice(adjustment: i8) -> Result { + use std::convert::TryFrom; + + unsafe { + *libc::__errno_location() = 0; + let niceness = libc::nice(libc::c_int::from(adjustment)); + let errno = *libc::__errno_location(); + if (niceness == -1) && (errno != 0) { + Err(errno) + } else { + Ok(niceness) + } + } + .map(|niceness| i8::try_from(niceness).expect("Unexpected niceness value")) + .map_err(nix::errno::from_i32) +} + +/// Adds `adjustment` to the nice value of calling thread. Negative `adjustment` increases priority, +/// positive `adjustment` decreases priority. New thread inherits nice value from current thread +/// when created. +/// +/// Fails on non-Linux systems for all `adjustment` values except of zero. +#[cfg(target_os = "linux")] +pub fn renice_this_thread(adjustment: i8) -> Result<(), String> { + // On Linux, the nice value is a per-thread attribute. See `man 7 sched` for details. + // Other systems probably should use pthread_setschedprio(), but, on Linux, thread priority + // is fixed to zero for SCHED_OTHER threads (which is the default). + nice(adjustment) + .map(|_| ()) + .map_err(|err| format!("Failed to change thread's nice value: {}", err)) +} + +/// Adds `adjustment` to the nice value of calling thread. Negative `adjustment` increases priority, +/// positive `adjustment` decreases priority. New thread inherits nice value from current thread +/// when created. +/// +/// Fails on non-Linux systems for all `adjustment` values except of zero. +#[cfg(not(target_os = "linux"))] +pub fn renice_this_thread(adjustment: i8) -> Result<(), String> { + if adjustment == 0 { + Ok(()) + } else { + Err(String::from( + "Failed to change thread's nice value: only supported on Linux", + )) + } +} + +/// Check whether the nice value can be changed by `adjustment`. +#[cfg(target_os = "linux")] +pub fn is_renice_allowed(adjustment: i8) -> bool { + use caps::{CapSet, Capability}; + + if adjustment >= 0 { + true + } else { + nix::unistd::geteuid().is_root() + || caps::has_cap(None, CapSet::Effective, Capability::CAP_SYS_NICE) + .map_err(|err| warn!("Failed to get thread's capabilities: {}", err)) + .unwrap_or(false) + } +} + +/// Check whether the nice value can be changed by `adjustment`. +#[cfg(not(target_os = "linux"))] +pub fn is_renice_allowed(adjustment: i8) -> bool { + adjustment == 0 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(target_os = "linux")] + #[test] + fn test_nice() { + // No change / get current niceness + let niceness = nice(0).unwrap(); + + // Decrease priority (allowed for unprivileged processes) + let result = std::thread::spawn(|| nice(1)).join().unwrap(); + assert_eq!(result, Ok(niceness + 1)); + + // Sanity check: ensure that current thread's nice value not changed after previous call + // from different thread + assert_eq!(nice(0), Ok(niceness)); + + // Sanity check: ensure that new thread inherits nice value from current thread + let inherited_niceness = std::thread::spawn(|| { + nice(1).unwrap(); + std::thread::spawn(|| nice(0).unwrap()).join().unwrap() + }) + .join() + .unwrap(); + assert_eq!(inherited_niceness, niceness + 1); + + if !is_renice_allowed(-1) { + // Increase priority (not allowed for unprivileged processes) + let result = std::thread::spawn(|| nice(-1)).join().unwrap(); + assert!(result.is_err()); + } + } +}