pub fn pin_thread(cores: &[usize]) {
if cores.is_empty() {
return;
}
#[cfg(target_os = "linux")]
unsafe {
pin_linux(cores);
}
#[cfg(target_os = "macos")]
unsafe {
pin_macos(cores[0]);
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
let _ = cores;
}
#[cfg(target_os = "linux")]
mod linux_ffi {
use std::ffi::c_int;
const CPU_SETSIZE: usize = 128;
unsafe extern "C" {
fn sched_setaffinity(
pid: c_int, cpusetsize: usize,
mask: *const u8,
) -> c_int;
}
pub unsafe fn pin(cores: &[usize]) -> std::io::Result<()> {
let mut set = [0u8; CPU_SETSIZE];
for &core in cores {
let byte = core / 8;
let bit = core % 8;
if byte < CPU_SETSIZE {
set[byte] |= 1u8 << bit;
}
}
let rc = unsafe { sched_setaffinity(0, CPU_SETSIZE, set.as_ptr()) };
if rc == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
}
#[cfg(target_os = "linux")]
unsafe fn pin_linux(cores: &[usize]) {
if let Err(e) = unsafe { linux_ffi::pin(cores) } {
eprintln!("ticklog: failed to pin thread to cores {cores:?}: {e}");
}
}
#[cfg(target_os = "macos")]
mod macos_ffi {
use std::ffi::{c_int, c_uint};
type Natural = c_uint; type Integer = c_int; type MachPort = Natural;
type KernReturn = c_int;
type PolicyFlavor = Natural;
type PolicyCount = Natural;
const THREAD_AFFINITY_POLICY: PolicyFlavor = 4;
const THREAD_AFFINITY_POLICY_COUNT: PolicyCount = 1;
#[repr(C)]
struct ThreadAffinityPolicy {
affinity_tag: Integer,
}
unsafe extern "C" {
static mach_task_self_: MachPort;
fn mach_thread_self() -> MachPort;
fn thread_policy_set(
thread: MachPort,
flavor: PolicyFlavor,
policy_info: *const ThreadAffinityPolicy,
count: PolicyCount,
) -> KernReturn;
fn mach_port_deallocate(task: MachPort, name: MachPort) -> KernReturn;
}
const KERN_SUCCESS: KernReturn = 0;
pub unsafe fn pin(tag: usize) {
let policy = ThreadAffinityPolicy {
affinity_tag: tag as Integer,
};
let thread = unsafe { mach_thread_self() };
let kr = unsafe {
thread_policy_set(
thread,
THREAD_AFFINITY_POLICY,
&policy as *const ThreadAffinityPolicy,
THREAD_AFFINITY_POLICY_COUNT,
)
};
if kr != KERN_SUCCESS {
eprintln!("ticklog: thread_policy_set failed to pin thread (kern_return {kr})");
}
unsafe { mach_port_deallocate(mach_task_self_, thread) };
}
}
#[cfg(target_os = "macos")]
unsafe fn pin_macos(tag: usize) {
unsafe { macos_ffi::pin(tag) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_slice_is_noop() {
pin_thread(&[]);
}
#[test]
fn single_core_does_not_panic() {
pin_thread(&[0]);
}
#[test]
fn multiple_cores_does_not_panic() {
pin_thread(&[0, 1, 2, 3]);
}
#[test]
fn successive_calls_does_not_panic() {
pin_thread(&[0]);
pin_thread(&[1, 2]);
pin_thread(&[]);
}
#[test]
fn large_core_number_does_not_panic() {
pin_thread(&[usize::MAX]);
}
#[cfg(target_os = "macos")]
#[test]
fn pin_does_not_leak_thread_port_refs() {
use std::ffi::{c_int, c_uint};
unsafe extern "C" {
static mach_task_self_: c_uint;
fn mach_thread_self() -> c_uint;
fn mach_port_deallocate(task: c_uint, name: c_uint) -> c_int;
fn mach_port_get_refs(
task: c_uint,
name: c_uint,
right: c_uint,
refs: *mut c_uint,
) -> c_int;
}
const MACH_PORT_RIGHT_SEND: c_uint = 0;
fn send_refs() -> u32 {
unsafe {
let thread = mach_thread_self();
let mut refs: c_uint = 0;
let kr =
mach_port_get_refs(mach_task_self_, thread, MACH_PORT_RIGHT_SEND, &mut refs);
assert_eq!(kr, 0, "mach_port_get_refs failed: {kr}");
mach_port_deallocate(mach_task_self_, thread);
refs
}
}
let before = send_refs();
for _ in 0..16 {
pin_thread(&[0]);
}
let after = send_refs();
assert_eq!(
after, before,
"pin_thread leaked {} thread-port send-right refs over 16 calls",
after as i64 - before as i64
);
}
#[cfg(target_os = "linux")]
#[test]
fn invalid_mask_reports_error() {
let result = unsafe { linux_ffi::pin(&[usize::MAX]) };
assert!(
result.is_err(),
"an all-zero affinity mask must report an error, got {result:?}"
);
}
}