#[derive(Copy, Clone)]
pub enum ThreadAffinityMode {
Off,
RoundRobin,
}
impl ThreadAffinityMode {
pub fn parse(value: &str) -> Option<Self> {
match value {
"off" => Some(Self::Off),
"round_robin" | "rr" => Some(Self::RoundRobin),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Off => "off",
Self::RoundRobin => "round_robin",
}
}
}
pub fn pin_worker_thread(_worker_index: usize, mode: ThreadAffinityMode) {
match mode {
ThreadAffinityMode::Off => {}
ThreadAffinityMode::RoundRobin => {
#[cfg(target_os = "linux")]
{
if let Some(cpu) = next_cpu(_worker_index) {
let _ = pin_current_thread_to_cpu(cpu);
}
}
}
}
}
#[cfg(target_os = "linux")]
fn next_cpu(worker_index: usize) -> Option<usize> {
use std::sync::OnceLock;
static CPUS: OnceLock<Vec<usize>> = OnceLock::new();
let cpus = CPUS.get_or_init(|| discover_allowed_cpus().unwrap_or_default());
if cpus.is_empty() {
None
} else {
Some(cpus[worker_index % cpus.len()])
}
}
#[cfg(target_os = "linux")]
fn discover_allowed_cpus() -> std::io::Result<Vec<usize>> {
let mut cpuset: libc::cpu_set_t = unsafe { std::mem::zeroed() };
let result = unsafe { libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut cpuset) };
if result != 0 {
return Err(std::io::Error::last_os_error());
}
let mut cpus = Vec::new();
for cpu in 0..(libc::CPU_SETSIZE as usize) {
if unsafe { libc::CPU_ISSET(cpu, &cpuset) } {
cpus.push(cpu);
}
}
Ok(cpus)
}
#[cfg(target_os = "linux")]
fn pin_current_thread_to_cpu(cpu: usize) -> std::io::Result<()> {
let mut cpuset: libc::cpu_set_t = unsafe { std::mem::zeroed() };
unsafe {
libc::CPU_ZERO(&mut cpuset);
libc::CPU_SET(cpu, &mut cpuset);
let thread = libc::pthread_self();
let result = libc::pthread_setaffinity_np(thread, std::mem::size_of::<libc::cpu_set_t>(), &cpuset);
if result != 0 {
return Err(std::io::Error::from_raw_os_error(result));
}
}
Ok(())
}