#[derive(Debug)]
pub enum AffinityResult {
Pinned(AffinityGuard),
NotPinned {
reason: String,
},
}
pub struct AffinityGuard {
#[cfg(target_os = "linux")]
original_mask: libc::cpu_set_t,
#[cfg(target_os = "linux")]
_pinned_cpu: usize,
#[cfg(target_os = "macos")]
_thread_port: u32,
}
impl AffinityGuard {
pub fn try_pin() -> AffinityResult {
#[cfg(target_os = "linux")]
{
Self::try_pin_linux()
}
#[cfg(target_os = "macos")]
{
Self::try_pin_macos()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
AffinityResult::NotPinned {
reason: "CPU affinity not supported on this platform".to_string(),
}
}
}
#[cfg(target_os = "linux")]
fn try_pin_linux() -> AffinityResult {
use std::mem::MaybeUninit;
unsafe {
let mut original_mask = MaybeUninit::<libc::cpu_set_t>::uninit();
let result = libc::sched_getaffinity(
0, std::mem::size_of::<libc::cpu_set_t>(),
original_mask.as_mut_ptr(),
);
if result != 0 {
return AffinityResult::NotPinned {
reason: format!(
"sched_getaffinity failed: {}",
std::io::Error::last_os_error()
),
};
}
let original_mask = original_mask.assume_init();
let current_cpu = libc::sched_getcpu();
if current_cpu < 0 {
return AffinityResult::NotPinned {
reason: format!("sched_getcpu failed: {}", std::io::Error::last_os_error()),
};
}
let mut new_mask: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut new_mask);
libc::CPU_SET(current_cpu as usize, &mut new_mask);
let result = libc::sched_setaffinity(
0, std::mem::size_of::<libc::cpu_set_t>(),
&new_mask,
);
if result != 0 {
return AffinityResult::NotPinned {
reason: format!(
"sched_setaffinity failed: {}",
std::io::Error::last_os_error()
),
};
}
tracing::debug!("Pinned thread to CPU {}", current_cpu);
AffinityResult::Pinned(AffinityGuard {
original_mask,
_pinned_cpu: current_cpu as usize,
})
}
}
#[cfg(target_os = "macos")]
fn try_pin_macos() -> AffinityResult {
unsafe {
let thread_port = libc::pthread_mach_thread_np(libc::pthread_self());
if thread_port == 0 {
return AffinityResult::NotPinned {
reason: "Failed to get mach thread port".to_string(),
};
}
#[repr(C)]
struct ThreadAffinityPolicy {
affinity_tag: i32,
}
const THREAD_AFFINITY_POLICY: u32 = 4;
const THREAD_AFFINITY_POLICY_COUNT: u32 = 1;
let policy = ThreadAffinityPolicy {
affinity_tag: 1, };
extern "C" {
fn thread_policy_set(
thread: u32,
flavor: u32,
policy_info: *const i32,
count: u32,
) -> i32;
}
let result = thread_policy_set(
thread_port,
THREAD_AFFINITY_POLICY,
&policy.affinity_tag as *const i32,
THREAD_AFFINITY_POLICY_COUNT,
);
if result != 0 {
let reason = match result {
46 => "macOS kernel policy is static (KERN_POLICY_STATIC) - affinity hints not supported on this system".to_string(),
4 => "Invalid argument to thread_policy_set".to_string(),
_ => format!("thread_policy_set failed with code {}", result),
};
return AffinityResult::NotPinned { reason };
}
tracing::debug!("Set macOS thread affinity hint (advisory)");
AffinityResult::Pinned(AffinityGuard {
_thread_port: thread_port,
})
}
}
}
#[cfg(target_os = "linux")]
impl Drop for AffinityGuard {
fn drop(&mut self) {
unsafe {
let result = libc::sched_setaffinity(
0,
std::mem::size_of::<libc::cpu_set_t>(),
&self.original_mask,
);
if result != 0 {
tracing::warn!(
"Failed to restore CPU affinity: {}",
std::io::Error::last_os_error()
);
} else {
tracing::debug!("Restored original CPU affinity");
}
}
}
}
#[cfg(target_os = "macos")]
impl Drop for AffinityGuard {
fn drop(&mut self) {
tracing::debug!("macOS affinity hint released (advisory, no restore needed)");
}
}
impl std::fmt::Debug for AffinityGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[cfg(target_os = "linux")]
{
f.debug_struct("AffinityGuard")
.field("pinned_cpu", &self._pinned_cpu)
.field("platform", &"linux")
.finish()
}
#[cfg(target_os = "macos")]
{
f.debug_struct("AffinityGuard")
.field("thread_port", &self._thread_port)
.field("platform", &"macos (advisory)")
.finish()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
f.debug_struct("AffinityGuard")
.field("platform", &"unsupported")
.finish()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_pin() {
let result = AffinityGuard::try_pin();
match result {
AffinityResult::Pinned(guard) => {
println!("Successfully pinned: {:?}", guard);
}
AffinityResult::NotPinned { reason } => {
println!("Not pinned (expected on some platforms): {}", reason);
}
}
}
#[test]
fn test_pin_and_restore() {
let guard = AffinityGuard::try_pin();
if let AffinityResult::Pinned(g) = guard {
std::hint::black_box(42);
drop(g);
let guard2 = AffinityGuard::try_pin();
assert!(
matches!(guard2, AffinityResult::Pinned(_)),
"Should be able to pin again after restore"
);
}
}
#[cfg(target_os = "linux")]
#[test]
fn test_linux_pin_to_current_cpu() {
use std::mem::MaybeUninit;
let guard = AffinityGuard::try_pin();
if let AffinityResult::Pinned(ref g) = guard {
unsafe {
let mut mask = MaybeUninit::<libc::cpu_set_t>::uninit();
let result = libc::sched_getaffinity(
0,
std::mem::size_of::<libc::cpu_set_t>(),
mask.as_mut_ptr(),
);
assert_eq!(result, 0, "sched_getaffinity should succeed");
let mask = mask.assume_init();
let mut count = 0;
for i in 0..libc::CPU_SETSIZE as usize {
if libc::CPU_ISSET(i, &mask) {
count += 1;
}
}
assert_eq!(count, 1, "Should be pinned to exactly one CPU");
assert_eq!(
g._pinned_cpu,
(0..libc::CPU_SETSIZE as usize)
.find(|&i| libc::CPU_ISSET(i, &mask))
.unwrap(),
"Pinned CPU should match"
);
}
}
}
}