1#![allow(unsafe_code)]
11
12use std::io;
13
14#[derive(Debug, thiserror::Error)]
16pub enum AffinityError {
17 #[error("sched_setaffinity failed: {0}")]
19 SetFailed(io::Error),
20
21 #[error("CPU {cpu} out of range (online: {online})")]
23 CpuOutOfRange {
24 cpu: usize,
26 online: usize,
28 },
29}
30
31#[cfg(target_os = "linux")]
40pub fn set_thread_affinity(cpu: usize) -> Result<(), AffinityError> {
41 let cpu_set_size = std::mem::size_of::<libc::cpu_set_t>() * 8;
43 if cpu >= cpu_set_size {
44 let online = online_cpu_count();
45 return Err(AffinityError::CpuOutOfRange { cpu, online });
46 }
47
48 unsafe {
51 let mut cpuset: libc::cpu_set_t = std::mem::zeroed();
52 libc::CPU_ZERO(&mut cpuset);
53 libc::CPU_SET(cpu, &mut cpuset);
54
55 let tid = libc::syscall(libc::SYS_gettid);
56 let ret = libc::sched_setaffinity(
57 tid as libc::pid_t,
58 std::mem::size_of::<libc::cpu_set_t>(),
59 &cpuset,
60 );
61
62 if ret != 0 {
63 return Err(AffinityError::SetFailed(io::Error::last_os_error()));
64 }
65 }
66 Ok(())
67}
68
69#[cfg(not(target_os = "linux"))]
71pub fn set_thread_affinity(_cpu: usize) -> Result<(), AffinityError> {
72 Ok(())
73}
74
75#[cfg(target_os = "linux")]
80pub fn set_thread_affinity_range(cpus: &[usize]) -> Result<(), AffinityError> {
81 let cpu_set_size = std::mem::size_of::<libc::cpu_set_t>() * 8;
83 for &cpu in cpus {
84 if cpu >= cpu_set_size {
85 let online = online_cpu_count();
86 return Err(AffinityError::CpuOutOfRange { cpu, online });
87 }
88 }
89
90 unsafe {
92 let mut cpuset: libc::cpu_set_t = std::mem::zeroed();
93 libc::CPU_ZERO(&mut cpuset);
94 for &cpu in cpus {
95 libc::CPU_SET(cpu, &mut cpuset);
96 }
97
98 let tid = libc::syscall(libc::SYS_gettid);
99 let ret = libc::sched_setaffinity(
100 tid as libc::pid_t,
101 std::mem::size_of::<libc::cpu_set_t>(),
102 &cpuset,
103 );
104
105 if ret != 0 {
106 return Err(AffinityError::SetFailed(io::Error::last_os_error()));
107 }
108 }
109 Ok(())
110}
111
112#[cfg(not(target_os = "linux"))]
114pub fn set_thread_affinity_range(_cpus: &[usize]) -> Result<(), AffinityError> {
115 Ok(())
116}
117
118#[cfg(target_os = "linux")]
123pub fn online_cpu_count() -> usize {
124 let n = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) };
126 if n <= 0 {
127 return 1;
128 }
129 n as usize
130}
131
132#[cfg(not(target_os = "linux"))]
134pub fn online_cpu_count() -> usize {
135 1
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_online_cpu_count_positive() {
144 let n = online_cpu_count();
145 assert!(n >= 1, "online CPU count must be >= 1, got {}", n);
146 }
147
148 #[cfg(target_os = "linux")]
149 #[test]
150 fn test_set_affinity_cpu_zero() {
151 let result = set_thread_affinity(0);
153 assert!(result.is_ok(), "set_thread_affinity(0) should succeed: {:?}", result);
154 }
155
156 #[cfg(target_os = "linux")]
157 #[test]
158 fn test_set_affinity_range_all_cpus() {
159 let n = online_cpu_count();
160 let cpus: Vec<usize> = (0..n).collect();
161 let result = set_thread_affinity_range(&cpus);
162 assert!(result.is_ok(), "set_thread_affinity_range(all) should succeed: {:?}", result);
163 }
164
165 #[cfg(target_os = "linux")]
166 #[test]
167 fn test_set_affinity_out_of_range() {
168 let result = set_thread_affinity(65535);
170 let _ = result;
172 }
173}