use windows::Win32::Foundation::{HANDLE, NTSTATUS};
use windows::Win32::System::SystemInformation::{GROUP_AFFINITY, GetSystemCpuSetInformation};
use windows::Win32::System::Threading::{
GetCurrentProcess, GetCurrentThread, SetThreadGroupAffinity, SetThreadPriority,
SetThreadSelectedCpuSets, THREAD_PRIORITY,
};
use super::scheduling_policy::SchedulingPolicy;
use crate::{
AffinityMask, AppliedPriority, Error, Grant, Mechanism, MechanismPolicy, Result, ThreadPriority,
};
const THREAD_GROUP_INFORMATION: i32 = 22;
#[link(name = "ntdll")]
unsafe extern "system" {
fn NtQueryInformationThread(
ThreadHandle: HANDLE,
ThreadInformationClass: i32,
ThreadInformation: *mut core::ffi::c_void,
ThreadInformationLength: u32,
ReturnLength: *mut u32,
) -> NTSTATUS;
}
pub(crate) fn set_thread_affinity(mask: &AffinityMask) -> Result<()> {
if mask.is_empty() {
return Err(Error::Affinity(
"Cannot set thread affinity with an empty mask".to_string(),
));
}
let mut group: Option<u16> = None;
let mut bits: usize = 0;
for lp in mask.iter() {
let g = (lp / 64) as u16;
match group {
None => group = Some(g),
Some(existing) if existing != g => {
return Err(Error::InvalidParameter(format!(
"Hard affinity is single-group on Windows (mask spans groups {} and {}); \
use soft affinity (CPU Sets) for cross-group placement",
existing, g
)));
}
_ => {}
}
bits |= 1usize << (lp % 64);
}
let ga = GROUP_AFFINITY {
Mask: bits,
Group: group.unwrap_or(0),
Reserved: [0; 3],
};
let ok = unsafe { SetThreadGroupAffinity(GetCurrentThread(), &ga, None) };
if !ok.as_bool() {
let err = std::io::Error::last_os_error();
return Err(Error::Affinity(format!(
"SetThreadGroupAffinity failed: {}",
err
)));
}
Ok(())
}
pub(crate) fn current_affinity() -> Result<AffinityMask> {
let mut ga = GROUP_AFFINITY::default();
let mut ret_len: u32 = 0;
let status = unsafe {
NtQueryInformationThread(
GetCurrentThread(),
THREAD_GROUP_INFORMATION,
(&raw mut ga).cast(),
std::mem::size_of::<GROUP_AFFINITY>() as u32,
&mut ret_len,
)
};
if status.is_err() {
return Err(Error::Affinity(format!(
"NtQueryInformationThread(ThreadGroupInformation) failed: {status:?}"
)));
}
let mut mask = AffinityMask::empty();
for bit in 0..64usize {
if (ga.Mask >> bit) & 1 != 0 {
mask.add(ga.Group as usize * 64 + bit);
}
}
Ok(mask)
}
pub(crate) fn set_thread_soft_affinity(mask: &AffinityMask) -> Result<()> {
if mask.is_empty() {
return Err(Error::Affinity(
"Cannot set soft affinity with an empty mask".to_string(),
));
}
let mut needed: u32 = 0;
unsafe {
let _ = GetSystemCpuSetInformation(None, 0, &mut needed, Some(GetCurrentProcess()), None);
}
if needed == 0 {
return Err(Error::Unsupported(
"CPU Sets are not available on this system".to_string(),
));
}
let mut buffer: Vec<u8> = vec![0; needed as usize];
let ok = unsafe {
GetSystemCpuSetInformation(
Some(buffer.as_mut_ptr() as *mut _),
needed,
&mut needed,
Some(GetCurrentProcess()),
None,
)
};
if !ok.as_bool() {
let err = std::io::Error::last_os_error();
return Err(Error::SystemCall(format!(
"GetSystemCpuSetInformation failed: {}",
err
)));
}
let mut ids: Vec<u32> = Vec::new();
let mut offset: usize = 0;
while offset + 8 <= needed as usize {
let size = u32::from_le_bytes(buffer[offset..offset + 4].try_into().unwrap()) as usize;
if size < 8 || offset + size > needed as usize {
break;
}
let rec_type = u32::from_le_bytes(buffer[offset + 4..offset + 8].try_into().unwrap());
if rec_type != 0 {
offset += size;
continue;
}
let payload = &buffer[offset + 8..offset + size];
if payload.len() >= 8 {
let id = u32::from_le_bytes(payload[0..4].try_into().unwrap());
let group = u16::from_le_bytes(payload[4..6].try_into().unwrap());
let lp_index = payload[6];
let os_id = group as usize * 64 + lp_index as usize;
if mask.contains(os_id) {
ids.push(id);
}
}
offset += size;
}
if ids.is_empty() {
return Err(Error::InvalidCoreId(
mask.iter().next().unwrap_or(usize::MAX),
));
}
let ok = unsafe { SetThreadSelectedCpuSets(GetCurrentThread(), &ids) };
if !ok.as_bool() {
let err = std::io::Error::last_os_error();
return Err(Error::SystemCall(format!(
"SetThreadSelectedCpuSets failed: {}",
err
)));
}
Ok(())
}
pub(crate) fn set_thread_priority(priority: ThreadPriority) -> Result<AppliedPriority> {
set_thread_priority_with_grant(priority, Grant::Direct)
}
pub(crate) fn promote_thread_to_realtime() -> Result<AppliedPriority> {
set_thread_priority_with_grant(ThreadPriority::TimeCritical, Grant::Realtime)
}
fn set_thread_priority_with_grant(
priority: ThreadPriority,
grant: Grant,
) -> Result<AppliedPriority> {
let sched_policy = SchedulingPolicy::default_for(priority);
let result = unsafe { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY(sched_policy.0)) };
match result {
Ok(_) => Ok(AppliedPriority::new(
priority,
priority,
grant,
Mechanism {
policy: MechanismPolicy::WinPriority,
value: sched_policy.0 as i8,
},
)),
Err(e) => Err(Error::SystemCall(format!(
"SetThreadPriority failed with error: {:?}",
e
))),
}
}