static HOME_MASK: std::sync::OnceLock<Option<libc::cpu_set_t>> = std::sync::OnceLock::new();
pub fn capture_home_mask() {
home_mask();
}
fn home_mask() -> Option<libc::cpu_set_t> {
*HOME_MASK.get_or_init(|| match current_mask() {
Ok(mask) => Some(mask),
Err(e) => {
tracing::warn!(
error = e,
"cannot read the process CPU mask; unpinned threads get every CPU"
);
None
}
})
}
fn current_mask() -> Result<libc::cpu_set_t, String> {
unsafe {
let mut mask: libc::cpu_set_t = std::mem::zeroed();
if libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut mask) != 0 {
return Err(format!(
"sched_getaffinity failed: {}",
std::io::Error::last_os_error()
));
}
Ok(mask)
}
}
fn all_cpus() -> libc::cpu_set_t {
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
for i in 0..libc::CPU_SETSIZE as usize {
libc::CPU_SET(i, &mut set);
}
set
}
}
fn set_mask(mask: &libc::cpu_set_t, what: &str) -> Result<(), String> {
let ret = unsafe { libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), mask) };
if ret != 0 {
return Err(format!(
"sched_setaffinity ({what}) failed: {}",
std::io::Error::last_os_error()
));
}
Ok(())
}
fn set_unpinned_mask() -> Result<(), String> {
let Some(home) = home_mask() else {
return set_mask(&all_cpus(), "unpin");
};
set_mask(&home, "unpin").or_else(|e| {
tracing::warn!(
error = e,
"the home CPU mask no longer applies; unpinning across every CPU instead"
);
set_mask(&all_cpus(), "unpin")
})
}
fn set_other_policy(what: &str) -> Result<(), String> {
let param = libc::sched_param { sched_priority: 0 };
if unsafe { libc::sched_setscheduler(0, libc::SCHED_OTHER, ¶m) } != 0 {
return Err(format!(
"sched_setscheduler ({what}) failed: {}",
std::io::Error::last_os_error()
));
}
Ok(())
}
pub fn pin_to_core(core_id: usize) -> Result<usize, String> {
home_mask();
const MAX_CPUS: usize = 1024;
if core_id >= MAX_CPUS {
return Err(format!("core_id {core_id} exceeds maximum ({MAX_CPUS})"));
}
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut set);
libc::CPU_SET(core_id, &mut set);
let ret = libc::sched_setaffinity(
0, std::mem::size_of::<libc::cpu_set_t>(),
&set,
);
if ret != 0 {
return Err(format!(
"sched_setaffinity failed for core {core_id}: {}",
std::io::Error::last_os_error()
));
}
}
if core_id > 0 && core_is_isolated(core_id) {
set_realtime_fifo(1);
} else if core_id > 0 {
tracing::warn!(
core = core_id,
"core not isolated (no isolcpus); pinned affinity only, no SCHED_FIFO \
(real-time busy-spin on a shared core would starve co-located threads). \
Boot with isolcpus on the pipeline cores for lowest tail latency."
);
}
Ok(core_id)
}
fn core_is_isolated(core_id: usize) -> bool {
match std::fs::read_to_string("/sys/devices/system/cpu/isolated") {
Ok(list) => cpu_list_contains(list.trim(), core_id),
Err(_) => false,
}
}
fn cpu_list_contains(list: &str, core_id: usize) -> bool {
list.split(',').filter(|p| !p.is_empty()).any(|part| {
match part.split_once('-') {
Some((lo, hi)) => matches!(
(lo.parse::<usize>(), hi.parse::<usize>()),
(Ok(lo), Ok(hi)) if lo <= core_id && core_id <= hi
),
None => matches!(part.parse::<usize>(), Ok(n) if n == core_id),
}
})
}
fn set_realtime_fifo(priority: i32) {
unsafe {
let param = libc::sched_param {
sched_priority: priority,
};
let ret = libc::sched_setscheduler(0, libc::SCHED_FIFO, ¶m);
if ret != 0 {
tracing::warn!(
error = %std::io::Error::last_os_error(),
"SCHED_FIFO failed (run as root or grant CAP_SYS_NICE)"
);
}
}
}
pub fn pin_thread(name: &str, core: usize) {
if core == 0 {
match clear_affinity() {
Ok(()) => tracing::info!(thread = name, "thread left unpinned, on the home CPU mask"),
Err(e) => tracing::warn!(thread = name, error = e, "could not unpin thread"),
}
return;
}
match pin_to_core(core) {
Ok(c) => tracing::info!(core = c, thread = name, "pinned to core"),
Err(e) => tracing::warn!(thread = name, error = e, "core pinning failed"),
}
}
pub struct SchedContext {
mask: libc::cpu_set_t,
policy: libc::c_int,
priority: libc::c_int,
}
pub fn take_context() -> Result<SchedContext, String> {
unsafe {
let mut mask: libc::cpu_set_t = std::mem::zeroed();
if libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut mask) != 0 {
return Err(format!(
"sched_getaffinity failed: {}",
std::io::Error::last_os_error()
));
}
let policy = libc::sched_getscheduler(0);
if policy < 0 {
return Err(format!(
"sched_getscheduler failed: {}",
std::io::Error::last_os_error()
));
}
let mut param: libc::sched_param = std::mem::zeroed();
if libc::sched_getparam(0, &mut param) != 0 {
return Err(format!(
"sched_getparam failed: {}",
std::io::Error::last_os_error()
));
}
Ok(SchedContext {
mask,
policy,
priority: param.sched_priority,
})
}
}
pub fn restore_context(ctx: &SchedContext) -> Result<(), String> {
unsafe {
let param = libc::sched_param {
sched_priority: ctx.priority,
};
if libc::sched_setscheduler(0, ctx.policy, ¶m) != 0 {
return Err(format!(
"sched_setscheduler (restore) failed: {}",
std::io::Error::last_os_error()
));
}
if libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &ctx.mask) != 0 {
return Err(format!(
"sched_setaffinity (restore) failed: {}",
std::io::Error::last_os_error()
));
}
Ok(())
}
}
pub fn prepare_child_context(core: usize) -> Result<(), String> {
home_mask();
const MAX_CPUS: usize = 1024;
if core >= MAX_CPUS {
return Err(format!("core {core} exceeds maximum ({MAX_CPUS})"));
}
set_other_policy("child prep")?;
if core == 0 {
return set_unpinned_mask();
}
let set = unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_SET(core, &mut set);
set
};
set_mask(&set, "child prep")
}
pub fn clear_affinity() -> Result<(), String> {
home_mask();
set_other_policy("clear")?;
set_unpinned_mask()
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
fn affinity_width() -> usize {
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
assert_eq!(
libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut set),
0,
"sched_getaffinity failed: {}",
std::io::Error::last_os_error()
);
(0..libc::CPU_SETSIZE as usize)
.filter(|&c| libc::CPU_ISSET(c, &set))
.count()
}
}
#[test]
fn child_inherits_parent_affinity_and_can_clear_its_own() {
let full_width = affinity_width();
if full_width < 2 {
assert!(pin_to_core(0).is_ok());
return;
}
let widths = std::thread::spawn(|| {
pin_to_core(0).expect("core 0 always exists");
let parent_before = affinity_width();
let child = std::thread::spawn(|| {
let inherited = affinity_width();
clear_affinity().expect("clear affinity");
(inherited, affinity_width())
})
.join()
.expect("child thread");
(parent_before, child, affinity_width())
})
.join()
.expect("parent thread");
let (parent_before, (child_inherited, child_cleared), parent_after) = widths;
assert_eq!(parent_before, 1, "the parent pinned itself to one core");
assert_eq!(
child_inherited, 1,
"child must inherit the parent's single-core mask — if this ever \
stops holding, the disk thread's clear_affinity call is obsolete"
);
assert_eq!(
child_cleared, full_width,
"clear_affinity must put the calling thread on the home mask"
);
assert_eq!(
parent_after, 1,
"the child's clear_affinity must not touch the parent's pin"
);
}
fn affinity_cores() -> Vec<usize> {
cores(¤t_mask().expect("sched_getaffinity"))
}
fn cores(set: &libc::cpu_set_t) -> Vec<usize> {
(0..libc::CPU_SETSIZE as usize)
.filter(|&c| unsafe { libc::CPU_ISSET(c, set) })
.collect()
}
#[test]
fn child_spawned_after_prepare_context_lands_on_its_own_core() {
if affinity_width() < 2 {
return; }
let (parent_before, child_cores, parent_after) = std::thread::spawn(|| {
pin_to_core(0).expect("core 0 always exists");
let parent_before = affinity_cores();
let saved = take_context().expect("snapshot context");
prepare_child_context(1).expect("prepare child context");
let child_cores = std::thread::spawn(affinity_cores)
.join()
.expect("child thread");
restore_context(&saved).expect("restore context");
(parent_before, child_cores, affinity_cores())
})
.join()
.expect("parent thread");
assert_eq!(parent_before, vec![0], "parent pinned itself to core 0");
assert_eq!(
child_cores,
vec![1],
"the child must start on the core it was prepared for — inheriting \
the parent's core is the deadlock this whole handover prevents"
);
assert_eq!(
parent_after, parent_before,
"the parent must get its own pin back after the spawn"
);
}
#[test]
fn an_unpinned_child_of_a_pinned_parent_gets_the_home_mask() {
capture_home_mask();
let home = cores(&home_mask().expect("the test thread's mask is readable"));
if home.len() < 2 {
return; }
let child_cores = std::thread::spawn(|| {
pin_to_core(0).expect("core 0 always exists");
let saved = take_context().expect("snapshot context");
prepare_child_context(0).expect("prepare unpinned child");
let child = std::thread::spawn(affinity_cores)
.join()
.expect("child thread");
restore_context(&saved).expect("restore context");
child
})
.join()
.expect("parent thread");
assert_eq!(
child_cores, home,
"an unpinned child must get the home mask, not the parent's pin"
);
}
#[test]
fn pin_thread_zero_puts_a_pinned_thread_back_on_the_home_mask() {
capture_home_mask();
let home = cores(&home_mask().expect("the test thread's mask is readable"));
if home.len() < 2 {
return; }
let (pinned, unpinned) = std::thread::spawn(|| {
pin_to_core(0).expect("core 0 always exists");
let pinned = affinity_cores();
pin_thread("unpinned-test", 0);
(pinned, affinity_cores())
})
.join()
.expect("thread");
assert_eq!(pinned, vec![0], "the thread pinned itself to core 0");
assert_eq!(
unpinned, home,
"pin_thread(_, 0) must put the thread back on the home mask"
);
}
#[test]
fn restore_context_round_trips_policy_and_priority() {
std::thread::spawn(|| {
let before = take_context().expect("snapshot");
let (policy_before, prio_before) = (before.policy, before.priority);
prepare_child_context(0).expect("prepare");
restore_context(&before).expect("restore");
let after = take_context().expect("snapshot again");
assert_eq!(after.policy, policy_before, "policy must round-trip");
assert_eq!(
after.priority, prio_before,
"priority must round-trip alongside the policy"
);
})
.join()
.expect("thread");
}
#[test]
fn pin_to_core_0_succeeds() {
assert!(pin_to_core(0).is_ok());
}
#[test]
fn pin_to_invalid_core_fails() {
assert!(pin_to_core(99999).is_err());
}
#[test]
fn cpu_list_membership() {
assert!(cpu_list_contains("2-7", 2));
assert!(cpu_list_contains("2-7", 7));
assert!(cpu_list_contains("2-7", 5));
assert!(!cpu_list_contains("2-7", 1));
assert!(!cpu_list_contains("2-7", 8));
assert!(cpu_list_contains("1,3,5", 3));
assert!(!cpu_list_contains("1,3,5", 4));
assert!(cpu_list_contains("2-4,6-8", 7));
assert!(cpu_list_contains("2-4,6-8", 3));
assert!(!cpu_list_contains("2-4,6-8", 5));
assert!(cpu_list_contains("0,2-4,9", 9));
assert!(!cpu_list_contains("", 0));
assert!(!cpu_list_contains("", 2));
assert!(!cpu_list_contains("x,2-", 2));
assert!(!cpu_list_contains("foo", 0));
}
}