use std::collections::BTreeSet;
pub const SYSFS_CPU: &str = "/sys/devices/system/cpu";
const ENV_WORKER_AFFINITY: &str = "MEMRA_WORKER_AFFINITY";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AffinitySpec {
Off,
Cpus(Vec<usize>),
SelfCcx,
Ccx(usize),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Topology {
pub domains: Vec<Vec<usize>>,
pub online: BTreeSet<usize>,
}
impl Topology {
pub fn read(root: &str) -> Self {
let mut cpus: Vec<usize> = Vec::new();
if let Ok(entries) = std::fs::read_dir(root) {
for entry in entries.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if let Some(Ok(idx)) = name.strip_prefix("cpu").map(str::parse::<usize>) {
cpus.push(idx);
}
}
}
cpus.sort_unstable();
let mut domains: Vec<Vec<usize>> = Vec::new();
let mut seen: Vec<String> = Vec::new();
let mut online = BTreeSet::new();
for cpu in cpus {
online.insert(cpu);
let path = format!("{root}/cpu{cpu}/cache/index3/shared_cpu_list");
let Ok(raw) = std::fs::read_to_string(&path) else {
continue;
};
let key = raw.trim().to_string();
if key.is_empty() {
continue;
}
if !seen.iter().any(|k| k == &key) {
let mut members = parse_cpu_list(&key).unwrap_or_default();
members.sort_unstable();
seen.push(key);
domains.push(members);
}
}
Topology { domains, online }
}
fn domain_of(&self, cpu: usize) -> Option<usize> {
self.domains.iter().position(|d| d.contains(&cpu))
}
}
pub fn parse_cpu_list(raw: &str) -> Result<Vec<usize>, String> {
let mut out = BTreeSet::new();
for part in raw.trim().split(',') {
let part = part.trim();
if part.is_empty() {
return Err(format!("empty range in cpu list {raw:?}"));
}
match part.split_once('-') {
Some((lo, hi)) => {
let lo: usize = lo
.trim()
.parse()
.map_err(|_| format!("bad range start {lo:?} in {raw:?}"))?;
let hi: usize = hi
.trim()
.parse()
.map_err(|_| format!("bad range end {hi:?} in {raw:?}"))?;
if hi < lo {
return Err(format!("descending range {part:?} in {raw:?}"));
}
for cpu in lo..=hi {
out.insert(cpu);
}
}
None => {
let cpu: usize = part
.parse()
.map_err(|_| format!("bad cpu {part:?} in {raw:?}"))?;
out.insert(cpu);
}
}
}
if out.is_empty() {
return Err(format!("cpu list {raw:?} selects no cpus"));
}
Ok(out.into_iter().collect())
}
pub fn render_cpu_list(cpus: &[usize]) -> String {
let mut out = String::new();
let mut idx = 0;
while idx < cpus.len() {
let start = cpus[idx];
let mut end = start;
while idx + 1 < cpus.len() && cpus[idx + 1] == end + 1 {
idx += 1;
end = cpus[idx];
}
if !out.is_empty() {
out.push(',');
}
if start == end {
out.push_str(&start.to_string());
} else {
out.push_str(&format!("{start}-{end}"));
}
idx += 1;
}
out
}
pub fn parse_affinity(raw: Option<&str>, topo: &Topology) -> Result<AffinitySpec, String> {
let Some(raw) = raw else {
return Ok(AffinitySpec::Off);
};
let value = raw.trim();
match value.to_ascii_lowercase().as_str() {
"" | "0" | "off" | "false" | "no" => return Ok(AffinitySpec::Off),
"ccx" | "1" | "on" | "self" => return Ok(AffinitySpec::SelfCcx),
_ => {}
}
if let Some(n) = value.to_ascii_lowercase().strip_prefix("ccx:") {
let idx: usize = n.trim().parse().map_err(|_| {
format!("{ENV_WORKER_AFFINITY}={value:?}: 'ccx:N' needs a number, got {n:?}")
})?;
if topo.domains.is_empty() {
return Err(format!(
"{ENV_WORKER_AFFINITY}={value:?}: this host exposes no L3 (index3) map in \
{SYSFS_CPU}, so 'ccx' forms cannot be resolved — use an explicit cpu list"
));
}
if idx >= topo.domains.len() {
return Err(format!(
"{ENV_WORKER_AFFINITY}={value:?}: this host has {} L3 domain(s) (0..{}), \
so domain {idx} does not exist",
topo.domains.len(),
topo.domains.len() - 1
));
}
return Ok(AffinitySpec::Ccx(idx));
}
let cpus = parse_cpu_list(value).map_err(|why| format!("{ENV_WORKER_AFFINITY}: {why}"))?;
if !topo.online.is_empty() {
let missing: Vec<usize> = cpus
.iter()
.copied()
.filter(|c| !topo.online.contains(c))
.collect();
if !missing.is_empty() {
return Err(format!(
"{ENV_WORKER_AFFINITY}={value:?}: cpu(s) {} are not present on this host \
(sysfs shows {} cpus)",
render_cpu_list(&missing),
topo.online.len()
));
}
}
Ok(AffinitySpec::Cpus(cpus))
}
pub fn worker_affinity_spec() -> Result<AffinitySpec, String> {
let raw = std::env::var(ENV_WORKER_AFFINITY).ok();
let topo = Topology::read(SYSFS_CPU);
parse_affinity(raw.as_deref(), &topo)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Applied {
pub requested: Vec<usize>,
pub effective: Vec<usize>,
pub l3_domains: usize,
}
#[cfg(target_os = "linux")]
pub fn apply(spec: &AffinitySpec) -> Result<Option<Applied>, String> {
let topo = Topology::read(SYSFS_CPU);
let requested: Vec<usize> = match spec {
AffinitySpec::Off => return Ok(None),
AffinitySpec::Cpus(cpus) => cpus.clone(),
AffinitySpec::Ccx(idx) => topo
.domains
.get(*idx)
.cloned()
.ok_or_else(|| format!("L3 domain {idx} vanished between validation and apply"))?,
AffinitySpec::SelfCcx => {
let cpu = current_cpu()?;
let idx = topo.domain_of(cpu).ok_or_else(|| {
format!("cpu {cpu} is in no L3 domain sysfs reports — cannot resolve 'ccx'")
})?;
topo.domains[idx].clone()
}
};
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut set);
for &cpu in &requested {
if cpu >= libc::CPU_SETSIZE as usize {
return Err(format!(
"cpu {cpu} is beyond CPU_SETSIZE ({})",
libc::CPU_SETSIZE
));
}
libc::CPU_SET(cpu, &mut set);
}
if libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &set) != 0 {
let err = std::io::Error::last_os_error();
return Err(format!(
"sched_setaffinity({}) failed: {err} — the thread stays where it was, \
so this boot is an OFF-arm boot and must not be filed as a pinned row",
render_cpu_list(&requested)
));
}
}
let effective = read_effective()?;
let l3_domains = {
let mut doms = BTreeSet::new();
for &cpu in &effective {
if let Some(d) = topo.domain_of(cpu) {
doms.insert(d);
}
}
doms.len()
};
Ok(Some(Applied {
requested,
effective,
l3_domains,
}))
}
#[cfg(not(target_os = "linux"))]
pub fn apply(spec: &AffinitySpec) -> Result<Option<Applied>, String> {
match spec {
AffinitySpec::Off => Ok(None),
_ => Err("CPU affinity is only implemented on Linux".to_string()),
}
}
#[cfg(target_os = "linux")]
fn read_effective() -> Result<Vec<usize>, String> {
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut set);
if libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut set) != 0 {
return Err(format!(
"sched_getaffinity failed: {}",
std::io::Error::last_os_error()
));
}
let mut out = Vec::new();
for cpu in 0..libc::CPU_SETSIZE as usize {
if libc::CPU_ISSET(cpu, &set) {
out.push(cpu);
}
}
Ok(out)
}
}
#[cfg(target_os = "linux")]
fn current_cpu() -> Result<usize, String> {
let cpu = unsafe { libc::sched_getcpu() };
if cpu < 0 {
return Err(format!(
"sched_getcpu failed: {}",
std::io::Error::last_os_error()
));
}
Ok(cpu as usize)
}
pub fn apply_and_announce(spec: &AffinitySpec) {
match apply(spec) {
Ok(None) => {
eprintln!("[worker-affinity] off (MEMRA_WORKER_AFFINITY unset or =0)");
}
Ok(Some(applied)) => {
let req = render_cpu_list(&applied.requested);
let eff = render_cpu_list(&applied.effective);
let clamped = if applied.requested == applied.effective {
""
} else {
" CLAMPED-BY-OUTER-CPUSET"
};
eprintln!(
"[worker-affinity] engaged request={req} effective={eff} cpus={} \
l3_domains={}{clamped}",
applied.effective.len(),
applied.l3_domains
);
}
Err(why) => {
eprintln!("[worker-affinity] REFUSED — {why}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn epyc_9654() -> Topology {
let domains = (0..12)
.map(|k| {
let mut v: Vec<usize> = (8 * k..8 * k + 8).collect();
v.extend(96 + 8 * k..96 + 8 * k + 8);
v
})
.collect::<Vec<_>>();
Topology {
domains,
online: (0..192).collect(),
}
}
#[test]
fn unset_and_disabled_values_make_no_syscall() {
let t = epyc_9654();
for raw in [
None,
Some(""),
Some("0"),
Some("off"),
Some(" OFF "),
Some("no"),
] {
assert_eq!(
parse_affinity(raw, &t).unwrap(),
AffinitySpec::Off,
"{raw:?} must be OFF — the default is OFF by design"
);
}
assert_eq!(apply(&AffinitySpec::Off).unwrap(), None);
}
#[test]
fn ccx_forms_resolve_against_the_hosts_own_l3_map() {
let t = epyc_9654();
assert_eq!(
parse_affinity(Some("ccx"), &t).unwrap(),
AffinitySpec::SelfCcx
);
assert_eq!(
parse_affinity(Some("on"), &t).unwrap(),
AffinitySpec::SelfCcx
);
assert_eq!(
parse_affinity(Some("ccx:3"), &t).unwrap(),
AffinitySpec::Ccx(3)
);
let err = parse_affinity(Some("ccx:12"), &t).unwrap_err();
assert!(err.contains("12 L3 domain(s)"), "{err}");
assert!(err.contains("does not exist"), "{err}");
}
#[test]
fn ccx_is_refused_when_sysfs_shows_no_l3_map() {
let blind = Topology {
domains: vec![],
online: (0..8).collect(),
};
let err = parse_affinity(Some("ccx:0"), &blind).unwrap_err();
assert!(err.contains("no L3"), "{err}");
assert!(err.contains("explicit cpu list"), "{err}");
}
#[test]
fn explicit_cpu_lists_parse_and_round_trip() {
assert_eq!(parse_cpu_list("3").unwrap(), vec![3]);
assert_eq!(parse_cpu_list("0-3,7").unwrap(), vec![0, 1, 2, 3, 7]);
assert_eq!(parse_cpu_list("7,0-3,2").unwrap(), vec![0, 1, 2, 3, 7]);
assert_eq!(render_cpu_list(&[8, 9, 10, 11, 12, 13, 14, 15]), "8-15");
assert_eq!(
render_cpu_list(&parse_cpu_list("8-15,104-111").unwrap()),
"8-15,104-111"
);
assert_eq!(render_cpu_list(&[1, 3, 4, 5, 9]), "1,3-5,9");
assert_eq!(render_cpu_list(&[]), "");
}
#[test]
fn malformed_values_are_startup_errors_never_a_silent_off() {
let t = epyc_9654();
for bad in ["8-15,1O4-111", "ccx:x", "15-8", "", ","] {
if bad.is_empty() {
continue; }
let got = parse_affinity(Some(bad), &t);
assert!(got.is_err(), "{bad:?} must be refused, got {got:?}");
}
}
#[test]
fn cpus_absent_from_this_host_are_refused() {
let t = epyc_9654();
let err = parse_affinity(Some("190-200"), &t).unwrap_err();
assert!(err.contains("not present on this host"), "{err}");
assert!(err.contains("192 cpus"), "{err}");
}
#[cfg(target_os = "linux")]
#[test]
fn apply_reports_the_kernel_readback_not_the_request() {
let current = read_effective().expect("sched_getaffinity must work");
assert!(!current.is_empty());
let applied = apply(&AffinitySpec::Cpus(current.clone()))
.expect("pinning to the mask we already have cannot fail")
.expect("a non-Off spec installs a mask");
assert_eq!(
applied.effective, current,
"effective comes from the kernel"
);
assert_eq!(applied.requested, current);
}
#[cfg(target_os = "linux")]
#[test]
fn self_ccx_resolves_on_the_calling_thread_or_says_why_not() {
match apply(&AffinitySpec::SelfCcx) {
Ok(Some(applied)) => {
assert!(!applied.effective.is_empty());
assert!(applied.l3_domains <= 1 || applied.requested != applied.effective);
}
Ok(None) => panic!("SelfCcx must never be a silent no-op"),
Err(why) => assert!(
why.contains("L3") || why.contains("sched_") || why.contains("cpu"),
"an unexplained refusal is the failure mode this lane exists to remove: {why}"
),
}
}
}