1use std::num::NonZeroUsize;
2
3pub fn available_parallelism() -> usize {
15 process_cpu_affinity_count()
16 .or_else(standard_available_parallelism)
17 .unwrap_or(1)
18}
19
20pub fn process_cpu_affinity_count() -> Option<usize> {
34 platform_process_cpu_affinity_count()
35}
36
37pub(crate) fn standard_available_parallelism() -> Option<usize> {
38 std::thread::available_parallelism()
39 .ok()
40 .map(NonZeroUsize::get)
41}
42
43#[cfg(any(target_os = "linux", target_os = "android", test))]
44fn count_affinity_mask_bits(mask: &[u8]) -> Option<usize> {
45 let count = mask.iter().map(|byte| byte.count_ones() as usize).sum();
46 (count > 0).then_some(count)
47}
48
49#[cfg(any(target_os = "linux", target_os = "android"))]
50const LINUX_EINVAL: i32 = 22;
51
52#[cfg(any(target_os = "linux", target_os = "android"))]
53fn linux_next_affinity_mask_bytes(mask_bytes: usize, errno: Option<i32>) -> Option<usize> {
54 (errno == Some(LINUX_EINVAL))
55 .then(|| mask_bytes.checked_mul(2))
56 .flatten()
57}
58
59#[cfg(any(target_os = "linux", target_os = "android"))]
60fn platform_process_cpu_affinity_count() -> Option<usize> {
61 unsafe extern "C" {
62 fn sched_getaffinity(pid: i32, cpusetsize: usize, mask: *mut core::ffi::c_void) -> i32;
63 }
64
65 const INITIAL_MASK_BYTES: usize = 128;
66
67 let mut mask_bytes = INITIAL_MASK_BYTES;
68 loop {
69 let mut mask = vec![0u8; mask_bytes];
70 let rc = unsafe {
73 sched_getaffinity(0, mask_bytes, mask.as_mut_ptr().cast::<core::ffi::c_void>())
74 };
75 if rc == 0 {
76 return count_affinity_mask_bits(&mask);
77 }
78
79 mask_bytes = linux_next_affinity_mask_bytes(
80 mask_bytes,
81 std::io::Error::last_os_error().raw_os_error(),
82 )?;
83 }
84}
85
86#[cfg(target_os = "windows")]
87fn platform_process_cpu_affinity_count() -> Option<usize> {
88 type Handle = *mut core::ffi::c_void;
89 type DwordPtr = usize;
90 type Word = u16;
91
92 unsafe extern "system" {
93 fn GetCurrentProcess() -> Handle;
94 fn GetProcessAffinityMask(
95 process: Handle,
96 process_affinity_mask: *mut DwordPtr,
97 system_affinity_mask: *mut DwordPtr,
98 ) -> i32;
99 fn GetActiveProcessorGroupCount() -> Word;
100 fn GetActiveProcessorCount(group_number: Word) -> u32;
101 fn GetProcessGroupAffinity(
102 process: Handle,
103 group_count: *mut Word,
104 group_array: *mut Word,
105 ) -> i32;
106 }
107
108 let process = unsafe { GetCurrentProcess() };
111 let system_group_count = unsafe { GetActiveProcessorGroupCount() };
113
114 if system_group_count <= 1 {
115 let mut process_mask = 0usize;
116 let mut system_mask = 0usize;
117 let ok = unsafe {
120 GetProcessAffinityMask(
121 process,
122 std::ptr::addr_of_mut!(process_mask),
123 std::ptr::addr_of_mut!(system_mask),
124 )
125 };
126 if ok != 0 {
127 let count = process_mask.count_ones() as usize;
128 return (count > 0).then_some(count);
129 }
130 let count = unsafe { GetActiveProcessorCount(0) } as usize;
132 return (count > 0).then_some(count);
133 }
134
135 let mut group_count: Word = 0;
136 let ok = unsafe {
141 GetProcessGroupAffinity(
142 process,
143 std::ptr::addr_of_mut!(group_count),
144 std::ptr::null_mut(),
145 )
146 };
147 if ok != 0 || group_count == 0 {
148 let count = unsafe { GetActiveProcessorCount(u16::MAX) } as usize;
150 return (count > 0).then_some(count);
151 }
152
153 let mut groups = vec![0u16; group_count as usize];
154 let ok = unsafe {
157 GetProcessGroupAffinity(
158 process,
159 std::ptr::addr_of_mut!(group_count),
160 groups.as_mut_ptr(),
161 )
162 };
163 if ok == 0 || group_count == 0 {
164 let count = unsafe { GetActiveProcessorCount(u16::MAX) } as usize;
166 return (count > 0).then_some(count);
167 }
168
169 if group_count == 1 {
170 let mut process_mask = 0usize;
171 let mut system_mask = 0usize;
172 let ok = unsafe {
175 GetProcessAffinityMask(
176 process,
177 std::ptr::addr_of_mut!(process_mask),
178 std::ptr::addr_of_mut!(system_mask),
179 )
180 };
181 if ok != 0 {
182 let count = process_mask.count_ones() as usize;
183 return (count > 0).then_some(count);
184 }
185 }
186
187 let count = groups
188 .into_iter()
189 .map(|group| {
190 (unsafe { GetActiveProcessorCount(group) }) as usize
192 })
193 .sum();
194 (count > 0).then_some(count)
195}
196
197#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "windows")))]
198fn platform_process_cpu_affinity_count() -> Option<usize> {
199 None
200}
201
202#[cfg(test)]
203mod tests;