use std::convert::TryFrom;
#[cfg(target_os = "android")]
use libc::SCHED_NORMAL as SCHED_OTHER;
#[cfg(not(target_os = "android"))]
use libc::SCHED_OTHER;
#[cfg(target_os = "vxworks")]
use libc::SCHED_SPORADIC;
#[cfg(any(target_os = "linux", target_os = "android"))]
use libc::{SCHED_BATCH, SCHED_IDLE};
use libc::{SCHED_FIFO, SCHED_RR};
use crate::{Error, ThreadPriority, ThreadPriorityValue};
use std::mem::MaybeUninit;
pub type ThreadId = libc::pthread_t;
pub const NICENESS_MAX: i8 = -20;
pub const NICENESS_MIN: i8 = 19;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct ScheduleParams {
pub sched_priority: libc::c_int,
}
fn errno() -> libc::c_int {
unsafe {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "android"))] {
*libc::__errno()
} else if #[cfg(target_os = "linux")] {
*libc::__errno_location()
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] {
*libc::__error()
} else if #[cfg(target_os = "vxworks")] {
libc::errnoGet()
} else {
compile_error!("Your OS is probably not supported.")
}
}
}
}
fn set_errno(number: libc::c_int) {
unsafe {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "android"))] {
*libc::__errno() = number;
} else if #[cfg(target_os = "linux")] {
*libc::__errno_location() = number;
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] {
*libc::__error() = number;
} else if #[cfg(target_os = "vxworks")] {
let _ = libc::errnoSet(number);
} else {
compile_error!("Your OS is probably not supported.")
}
}
}
}
fn do_with_errno<F: FnOnce() -> libc::c_int>(f: F) -> Result<libc::c_int, Error> {
let return_value = f();
if return_value < 0 {
Err(Error::OS(errno()))
} else {
Ok(return_value)
}
}
#[derive(Debug, Default)]
#[cfg(any(target_os = "linux", target_os = "android"))]
#[repr(C)]
pub struct SchedAttr {
size: u32,
sched_policy: u32,
sched_flags: u64,
sched_nice: i32,
sched_priority: u32,
sched_runtime: u64,
sched_deadline: u64,
sched_period: u64,
sched_util_min: u32,
sched_util_max: u32,
}
impl ScheduleParams {
fn into_posix(self) -> libc::sched_param {
let mut param = unsafe { MaybeUninit::<libc::sched_param>::zeroed().assume_init() };
param.sched_priority = self.sched_priority;
param
}
fn from_posix(sched_param: libc::sched_param) -> Self {
ScheduleParams {
sched_priority: sched_param.sched_priority,
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeadlineFlags: u64 {
const RESET_ON_FORK = 0x01;
const RECLAIM = 0x02;
const DEADLINE_OVERRUN = 0x04;
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_thread_scheduling_attributes() -> Result<SchedAttr, Error> {
let mut sched_attr = SchedAttr::default();
let current_thread = 0;
let flags = 0;
let ret = unsafe {
libc::syscall(
libc::SYS_sched_getattr,
current_thread,
&mut sched_attr as *mut _,
std::mem::size_of::<SchedAttr>() as u32,
flags,
)
};
if ret < 0 {
return Err(Error::OS(errno()));
}
Ok(sched_attr)
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RealtimeThreadSchedulePolicy {
Fifo,
RoundRobin,
#[cfg(target_os = "vxworks")]
Sporadic,
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
Deadline,
}
impl RealtimeThreadSchedulePolicy {
fn to_posix(self) -> libc::c_int {
match self {
RealtimeThreadSchedulePolicy::Fifo => SCHED_FIFO,
RealtimeThreadSchedulePolicy::RoundRobin => SCHED_RR,
#[cfg(target_os = "vxworks")]
RealtimeThreadSchedulePolicy::Sporadic => SCHED_SPORADIC,
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
RealtimeThreadSchedulePolicy::Deadline => 6,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum NormalThreadSchedulePolicy {
#[cfg(any(target_os = "linux", target_os = "android"))]
Idle,
#[cfg(any(target_os = "linux", target_os = "android"))]
Batch,
Other,
}
impl NormalThreadSchedulePolicy {
fn to_posix(self) -> libc::c_int {
match self {
#[cfg(any(target_os = "linux", target_os = "android"))]
NormalThreadSchedulePolicy::Idle => SCHED_IDLE,
#[cfg(any(target_os = "linux", target_os = "android"))]
NormalThreadSchedulePolicy::Batch => SCHED_BATCH,
NormalThreadSchedulePolicy::Other => SCHED_OTHER,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ThreadSchedulePolicy {
Normal(NormalThreadSchedulePolicy),
Realtime(RealtimeThreadSchedulePolicy),
}
impl ThreadSchedulePolicy {
fn to_posix(self) -> libc::c_int {
match self {
ThreadSchedulePolicy::Normal(p) => p.to_posix(),
ThreadSchedulePolicy::Realtime(p) => p.to_posix(),
}
}
fn from_posix(policy: libc::c_int) -> Result<ThreadSchedulePolicy, Error> {
match policy {
SCHED_OTHER => Ok(ThreadSchedulePolicy::Normal(
NormalThreadSchedulePolicy::Other,
)),
#[cfg(any(target_os = "linux", target_os = "android"))]
SCHED_BATCH => Ok(ThreadSchedulePolicy::Normal(
NormalThreadSchedulePolicy::Batch,
)),
#[cfg(any(target_os = "linux", target_os = "android"))]
SCHED_IDLE => Ok(ThreadSchedulePolicy::Normal(
NormalThreadSchedulePolicy::Idle,
)),
SCHED_FIFO => Ok(ThreadSchedulePolicy::Realtime(
RealtimeThreadSchedulePolicy::Fifo,
)),
SCHED_RR => Ok(ThreadSchedulePolicy::Realtime(
RealtimeThreadSchedulePolicy::RoundRobin,
)),
#[cfg(target_os = "vxworks")]
SCHED_SPORADIC => Ok(ThreadSchedulePolicy::Realtime(
RealtimeThreadSchedulePolicy::Sporadic,
)),
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
6 => Ok(ThreadSchedulePolicy::Realtime(
RealtimeThreadSchedulePolicy::Deadline,
)),
_ => Err(Error::Ffi("Can't parse schedule policy from posix")),
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum PriorityPolicyEdgeValueType {
Minimum,
Maximum,
}
impl ThreadPriority {
pub fn max_value_for_policy(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Maximum)
}
pub fn min_value_for_policy(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Minimum)
}
fn get_edge_value_for_policy(
policy: ThreadSchedulePolicy,
edge: PriorityPolicyEdgeValueType,
) -> Result<libc::c_int, Error> {
let get_edge_priority = match edge {
PriorityPolicyEdgeValueType::Minimum => Self::get_min_priority,
PriorityPolicyEdgeValueType::Maximum => Self::get_max_priority,
};
match policy {
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
allow(unused_variables)
)]
ThreadSchedulePolicy::Normal(normal) => {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
if normal == NormalThreadSchedulePolicy::Idle {
Ok(0)
} else {
Ok(match edge {
PriorityPolicyEdgeValueType::Minimum => NICENESS_MIN as libc::c_int,
PriorityPolicyEdgeValueType::Maximum => NICENESS_MAX as libc::c_int,
})
}
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "vxworks"))] {
get_edge_priority(policy)
} else {
Err(Error::Priority(
"Unsupported thread priority for this OS. Change the scheduling policy or use a supported OS.",
))
}
}
}
_ => get_edge_priority(policy),
}
}
fn get_max_priority(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
do_with_errno(|| unsafe { libc::sched_get_priority_max(policy.to_posix()) })
}
fn get_min_priority(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
do_with_errno(|| unsafe { libc::sched_get_priority_min(policy.to_posix()) })
}
pub fn to_allowed_value_for_policy(
priority: libc::c_int,
policy: ThreadSchedulePolicy,
) -> Result<libc::c_int, Error> {
let min_priority = Self::min_value_for_policy(policy)?;
let max_priority = Self::max_value_for_policy(policy)?;
let (min, max) = (
std::cmp::min(min_priority, max_priority),
std::cmp::max(min_priority, max_priority),
);
let allowed_range = min..=max;
if allowed_range.contains(&priority) {
Ok(priority)
} else {
Err(Error::PriorityNotInRange(allowed_range))
}
}
pub fn to_posix(self, policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
let ret = match self {
ThreadPriority::Min => match policy {
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
Error::Priority("Deadline scheduling must use deadline priority."),
),
_ => Self::min_value_for_policy(policy).map(|v| v as u32),
},
ThreadPriority::Crossplatform(ThreadPriorityValue(p)) => match policy {
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
Error::Priority("Deadline scheduling must use deadline priority."),
),
ThreadSchedulePolicy::Realtime(_) => {
Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32)
}
#[cfg(all(
any(target_os = "macos", target_os = "ios", target_os = "vxworks"),
not(target_arch = "wasm32")
))]
ThreadSchedulePolicy::Normal(_) => {
Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32)
}
#[cfg(not(all(
any(target_os = "macos", target_os = "ios", target_os = "vxworks"),
not(target_arch = "wasm32")
)))]
ThreadSchedulePolicy::Normal(_) => {
let niceness_values = NICENESS_MAX.abs() + NICENESS_MIN.abs();
let ratio = 1f32 - (p as f32 / ThreadPriorityValue::MAX.0 as f32);
let niceness = ((niceness_values as f32 * ratio) as i8 + NICENESS_MAX) as i32;
Self::to_allowed_value_for_policy(niceness, policy).map(|v| v as u32)
}
},
ThreadPriority::Os(crate::ThreadPriorityOsValue(p)) => match policy {
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
Error::Priority("Deadline scheduling must use deadline priority."),
),
_ => Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32),
},
ThreadPriority::Max => match policy {
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
Error::Priority("Deadline scheduling must use deadline priority."),
),
_ => Self::max_value_for_policy(policy).map(|v| v as u32),
},
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "wasm32")
))]
ThreadPriority::Deadline { .. } => Err(Error::Priority(
"Deadline is non-POSIX and cannot be converted.",
)),
};
ret.map(|p| p as libc::c_int)
}
pub fn from_posix(params: ScheduleParams) -> ThreadPriority {
ThreadPriority::Crossplatform(ThreadPriorityValue(params.sched_priority as u8))
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn set_thread_priority_and_policy_deadline(
native: ThreadId,
priority: ThreadPriority,
) -> Result<(), Error> {
use std::convert::TryInto as _;
let (runtime, deadline, period, flags) = match priority {
ThreadPriority::Deadline {
runtime,
deadline,
period,
flags,
} => (|| {
Ok((
runtime.as_nanos().try_into()?,
deadline.as_nanos().try_into()?,
period.as_nanos().try_into()?,
flags,
))
})()
.map_err(|_: std::num::TryFromIntError| {
Error::Priority("Deadline policy durations don't fit into a `u64`.")
})?,
_ => {
return Err(Error::Priority(
"Deadline policy given without deadline priority.",
));
}
};
let tid = native as libc::pid_t;
let sched_attr = SchedAttr {
size: std::mem::size_of::<SchedAttr>() as u32,
sched_policy: RealtimeThreadSchedulePolicy::Deadline.to_posix() as u32,
sched_runtime: runtime,
sched_deadline: deadline,
sched_period: period,
sched_flags: flags.bits(),
..Default::default()
};
let ret =
unsafe { libc::syscall(libc::SYS_sched_setattr, tid, &sched_attr as *const _, 0) as i32 };
match ret {
0 => Ok(()),
e => Err(Error::OS(e)),
}
}
pub fn set_thread_priority_and_policy(
native: ThreadId,
priority: ThreadPriority,
policy: ThreadSchedulePolicy,
) -> Result<(), Error> {
match policy {
#[cfg(any(target_os = "linux", target_os = "android"))]
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => {
set_thread_priority_and_policy_deadline(native, priority)
}
_ => {
let fixed_priority = priority.to_posix(policy)?;
if matches!(policy, ThreadSchedulePolicy::Realtime(_))
|| cfg!(any(
target_os = "macos",
target_os = "ios",
target_os = "vxworks"
))
{
let params = ScheduleParams {
sched_priority: fixed_priority,
}
.into_posix();
let ret = unsafe {
libc::pthread_setschedparam(
native,
policy.to_posix(),
¶ms as *const libc::sched_param,
)
};
match ret {
0 => Ok(()),
e => Err(Error::OS(e)),
}
} else {
#[cfg(target_os = "vxworks")]
unsafe fn setpriority(
_which: u32,
_who: u32,
_priority: libc::c_int,
) -> libc::c_int {
set_errno(libc::ENOSYS);
-1
}
#[cfg(not(target_os = "vxworks"))]
use libc::setpriority;
let params = ScheduleParams { sched_priority: 0 }.into_posix();
let ret = unsafe {
libc::pthread_setschedparam(
native,
policy.to_posix(),
¶ms as *const libc::sched_param,
)
};
if ret != 0 {
return Err(Error::OS(ret));
}
set_errno(0);
let ret = unsafe { setpriority(libc::PRIO_PROCESS, 0, fixed_priority) };
if ret != 0 {
return Err(Error::OS(errno()));
}
Ok(())
}
}
}
}
pub fn set_current_thread_priority(priority: ThreadPriority) -> Result<(), Error> {
let thread_id = thread_native_id();
let policy = thread_schedule_policy()?;
set_thread_priority_and_policy(thread_id, priority, policy)
}
pub fn thread_schedule_policy() -> Result<ThreadSchedulePolicy, Error> {
thread_schedule_policy_param(thread_native_id()).map(|policy| policy.0)
}
pub fn thread_schedule_policy_param(
native: ThreadId,
) -> Result<(ThreadSchedulePolicy, ScheduleParams), Error> {
unsafe {
let mut policy = 0i32;
let mut params = ScheduleParams { sched_priority: 0 }.into_posix();
let ret = libc::pthread_getschedparam(
native,
&mut policy as *mut libc::c_int,
&mut params as *mut libc::sched_param,
);
match ret {
0 => Ok((
ThreadSchedulePolicy::from_posix(policy)?,
ScheduleParams::from_posix(params),
)),
e => Err(Error::OS(e)),
}
}
}
pub fn get_thread_priority(native: ThreadId) -> Result<ThreadPriority, Error> {
Ok(ThreadPriority::from_posix(
thread_schedule_policy_param(native)?.1,
))
}
pub fn get_current_thread_priority() -> Result<ThreadPriority, Error> {
get_thread_priority(thread_native_id())
}
pub trait ThreadExt {
fn get_priority(&self) -> Result<ThreadPriority, Error> {
get_current_thread_priority()
}
fn set_priority(&self, priority: ThreadPriority) -> Result<(), Error> {
priority.set_for_current()
}
fn get_schedule_policy(&self) -> Result<ThreadSchedulePolicy, Error> {
thread_schedule_policy()
}
fn get_schedule_policy_param(&self) -> Result<(ThreadSchedulePolicy, ScheduleParams), Error> {
thread_schedule_policy_param(thread_native_id())
}
fn set_priority_and_policy(
&self,
policy: ThreadSchedulePolicy,
priority: ThreadPriority,
) -> Result<(), Error> {
cfg_if::cfg_if! {
if #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_arch = "wasm32")))] {
if policy == ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) {
set_thread_priority_and_policy(thread_native_id(), ThreadPriority::Crossplatform(ThreadPriorityValue(0)), policy)
} else {
set_thread_priority_and_policy(thread_native_id(), priority, policy)
}
} else {
set_thread_priority_and_policy(thread_native_id(), priority, policy)
}
}
}
fn get_native_id(&self) -> Result<ThreadId, Error>;
}
impl ThreadExt for std::thread::Thread {
fn get_native_id(&self) -> Result<ThreadId, Error> {
if self.id() == std::thread::current().id() {
Ok(thread_native_id())
} else {
Err(Error::Priority(
"The `ThreadExt::get_native_id()` is currently limited to be called on the current thread.",
))
}
}
}
pub fn thread_native_id() -> ThreadId {
unsafe { libc::pthread_self() }
}
impl TryFrom<u8> for ThreadPriority {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if let 0..=100 = value {
Ok(ThreadPriority::Crossplatform(ThreadPriorityValue(value)))
} else {
Err("The thread priority value must be in range of [0; 100].")
}
}
}
#[cfg(test)]
mod tests {
use crate::unix::*;
#[test]
fn thread_schedule_policy_param_test() {
let thread_id = thread_native_id();
assert!(thread_schedule_policy_param(thread_id).is_ok());
}
#[test]
fn change_between_realtime_and_normal_policies_requires_capabilities() {
use crate::ThreadPriorityOsValue;
const TEST_PRIORITY: u8 = 15;
let realtime_policy = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo);
let normal_policy = ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other);
let desired_priority = ThreadPriority::Os(ThreadPriorityOsValue(TEST_PRIORITY as _));
let expected_priority = ThreadPriority::Crossplatform(ThreadPriorityValue(TEST_PRIORITY));
let thread = std::thread::current();
thread
.set_priority_and_policy(realtime_policy, desired_priority)
.expect("to set realtime fifo policy");
assert_eq!(thread.get_schedule_policy(), Ok(realtime_policy));
assert_eq!(thread.get_priority(), Ok(expected_priority));
thread
.set_priority_and_policy(normal_policy, desired_priority)
.expect("to set normal other policy");
assert_eq!(thread.get_schedule_policy(), Ok(normal_policy));
#[cfg(not(target_os = "linux"))]
assert_eq!(thread.get_priority(), Ok(expected_priority));
#[cfg(target_os = "linux")]
{
let nice = unsafe { libc::getpriority(0, 0) };
assert_eq!(nice, TEST_PRIORITY as i32);
}
}
#[test]
#[cfg(target_os = "linux")]
fn set_deadline_policy() {
#![allow(clippy::identity_op)]
use std::time::Duration;
assert!(
set_thread_priority_and_policy(
0, ThreadPriority::Deadline {
runtime: Duration::from_millis(1),
deadline: Duration::from_millis(10),
period: Duration::from_millis(100),
flags: DeadlineFlags::RESET_ON_FORK,
},
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline)
)
.is_ok()
);
let attributes = get_thread_scheduling_attributes().unwrap();
assert_eq!(
attributes.sched_policy,
RealtimeThreadSchedulePolicy::Deadline.to_posix() as u32
);
assert_eq!(attributes.sched_runtime, 1 * 10_u64.pow(6));
assert_eq!(attributes.sched_deadline, 10 * 10_u64.pow(6));
assert_eq!(attributes.sched_period, 100 * 10_u64.pow(6));
assert_eq!(attributes.sched_flags, DeadlineFlags::RESET_ON_FORK.bits());
}
}