use libc::{SYS_gettid, c_int, syscall};
use crate::{
AffinityMask, AppliedPriority, BrokerError, Error, FallbackReason, Grant, Mechanism,
MechanismPolicy, Result, ThreadPriority,
platform::linux::scheduling_policy::{level_for_nice, nice_for},
};
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 max_cpus = libc::CPU_SETSIZE as usize;
let mut cpuset: libc::cpu_set_t = unsafe { std::mem::zeroed() };
unsafe {
libc::CPU_ZERO(&mut cpuset);
}
for core_idx in mask.iter() {
if core_idx < max_cpus {
unsafe {
libc::CPU_SET(core_idx, &mut cpuset);
}
} else {
return Err(Error::Affinity(format!(
"OS core {} exceeds CPU_SETSIZE {}",
core_idx, max_cpus
)));
}
}
let res =
unsafe { libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &cpuset) };
if res == -1 {
let err = std::io::Error::last_os_error();
Err(map_sched_setaffinity_error(err))
} else {
Ok(())
}
}
fn map_sched_setaffinity_error(err: std::io::Error) -> Error {
match err.raw_os_error() {
Some(libc::EINVAL) => Error::InvalidParameter(format!(
"Invalid affinity mask for sched_setaffinity: {}",
err
)),
_ => Error::Affinity(format!("sched_setaffinity failed: {}", err)),
}
}
pub(crate) fn current_affinity() -> Result<AffinityMask> {
let mut cpuset: libc::cpu_set_t = unsafe { std::mem::zeroed() };
let res =
unsafe { libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut cpuset) };
if res == -1 {
let err = std::io::Error::last_os_error();
return Err(Error::Affinity(format!("sched_getaffinity failed: {err}")));
}
let mut mask = AffinityMask::empty();
for cpu in 0..(libc::CPU_SETSIZE as usize) {
if unsafe { libc::CPU_ISSET(cpu, &cpuset) } {
mask.add(cpu);
}
}
Ok(mask)
}
pub(crate) fn current_tid() -> Result<libc::id_t> {
let tid = unsafe { syscall(SYS_gettid) };
if tid <= 0 {
let err = std::io::Error::last_os_error();
return Err(Error::SystemCall(format!(
"Failed to get thread ID via gettid(): {}",
err
)));
}
Ok(tid as libc::id_t)
}
pub(crate) fn current_nice() -> Result<c_int> {
let tid = current_tid()?;
let rc = unsafe { libc::syscall(libc::SYS_getpriority, libc::PRIO_PROCESS, tid as c_int) };
if rc < 0 {
let err = std::io::Error::last_os_error();
return Err(Error::SystemCall(format!(
"getpriority for TID {}: {}",
tid, err
)));
}
Ok(20 - rc as c_int)
}
fn set_thread_nice_value(nice_value: c_int) -> Result<()> {
let tid = current_tid()?;
let res = unsafe { libc::setpriority(libc::PRIO_PROCESS, tid, nice_value) };
if res == -1 {
let err = std::io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EACCES | libc::EPERM) => Err(Error::PermissionDenied(format!(
"Setting nice value {} for TID {}: {}",
nice_value, tid, err
))),
Some(libc::ESRCH) => Err(Error::NotFound(format!(
"Thread with TID {} not found for setpriority: {}",
tid, err
))),
Some(libc::EINVAL) => Err(Error::InvalidParameter(format!(
"Invalid nice value {} for setpriority",
nice_value
))),
_ => Err(Error::SystemCall(format!(
"setpriority failed for nice value {} for TID {}: {}",
nice_value, tid, err
))),
}
} else {
Ok(())
}
}
pub(crate) fn set_thread_priority(priority: ThreadPriority) -> Result<AppliedPriority> {
let value = nice_for(priority);
match set_thread_nice_value(value) {
Ok(()) => Ok(AppliedPriority::new(
priority,
priority,
Grant::Direct,
Mechanism {
policy: MechanismPolicy::Nice,
value: value as i8,
},
)),
Err(Error::PermissionDenied(_)) => {
#[cfg_attr(not(feature = "rtkit"), allow(unused_mut))]
let mut reason = FallbackReason::NoBroker;
#[cfg_attr(not(feature = "rtkit"), allow(unused_mut))]
let mut broker_error: Option<BrokerError> = None;
#[cfg(feature = "rtkit")]
{
if let Ok(tid) = current_tid() {
match crate::platform::linux::rtkit::try_high_priority(tid as u64, value) {
Ok(granted) if granted == value => {
return Ok(AppliedPriority::new(
priority,
priority,
Grant::Brokered,
Mechanism {
policy: MechanismPolicy::Nice,
value: granted as i8,
},
));
}
Ok(granted) => {
return Ok(AppliedPriority::new(
priority,
priority,
Grant::Brokered,
Mechanism {
policy: MechanismPolicy::Nice,
value: granted as i8,
},
)
.with_reason(FallbackReason::Clamped));
}
Err((r, be)) => {
reason = r;
broker_error = be;
}
}
}
}
let current = current_nice().unwrap_or(nice_for(ThreadPriority::Normal));
let mut applied = AppliedPriority::new(
priority,
level_for_nice(current),
Grant::Direct,
Mechanism {
policy: MechanismPolicy::Nice,
value: current as i8,
},
)
.with_reason(reason);
if let Some(be) = broker_error {
applied = applied.with_broker_error(be);
}
Ok(applied)
}
Err(other) => Err(other),
}
}
pub(crate) fn set_thread_realtime_rr(priority: c_int) -> Result<()> {
let (rt_min, rt_max) = unsafe {
(
libc::sched_get_priority_min(libc::SCHED_RR),
libc::sched_get_priority_max(libc::SCHED_RR),
)
};
if rt_min == -1 || rt_max == -1 {
let err = std::io::Error::last_os_error();
return Err(Error::SystemCall(format!(
"Failed to get SCHED_RR priority range: {}",
err
)));
}
if priority < rt_min || priority > rt_max {
return Err(Error::InvalidParameter(format!(
"Absolute priority {} is out of range [{}, {}] for SCHED_RR",
priority, rt_min, rt_max
)));
}
let current_thread = unsafe { libc::pthread_self() };
let mut param: libc::sched_param = unsafe { std::mem::zeroed() };
param.sched_priority = priority;
let policy = libc::SCHED_RR | libc::SCHED_RESET_ON_FORK;
let res = unsafe { libc::pthread_setschedparam(current_thread, policy, ¶m) };
if res != 0 {
let err = std::io::Error::from_raw_os_error(res); match res {
libc::EPERM => Err(Error::PermissionDenied(format!(
"Setting SCHED_RR with priority {}: {}",
priority, err
))),
libc::EINVAL => Err(Error::InvalidParameter(format!(
"Invalid parameters for SCHED_RR: priority={}. Error: {}",
priority, err
))),
libc::ESRCH => Err(Error::NotFound(format!(
"Thread not found for pthread_setschedparam. Error: {}",
err
))),
_ => Err(Error::SystemCall(format!(
"pthread_setschedparam failed for SCHED_RR with priority {}. Error: {}",
priority, err
))),
}
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sched_setaffinity_einval_maps_to_invalid_parameter() {
let err = std::io::Error::from_raw_os_error(libc::EINVAL);
assert!(matches!(
map_sched_setaffinity_error(err),
Error::InvalidParameter(_)
));
}
}