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(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 = "freebsd"))] {
*libc::__error()
} 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 = "freebsd"))] {
*libc::__error() = number;
} else {
compile_error!("Your OS is probably not supported.")
}
}
}
}
#[repr(C)]
#[derive(Debug, Default)]
#[cfg(any(target_os = "linux", target_os = "android"))]
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,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RealtimeThreadSchedulePolicy {
Fifo,
RoundRobin,
#[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(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(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")),
}
}
}
impl ThreadPriority {
pub fn max_value_for_policy(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
match policy {
#[cfg(any(target_os = "linux", target_os = "android"))]
ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle) => {
Ok(0)
}
ThreadSchedulePolicy::Normal(_) => {
Ok(NICENESS_MAX as libc::c_int)
}
_ => {
let max_priority = unsafe { libc::sched_get_priority_max(policy.to_posix()) };
if max_priority < 0 {
Err(Error::OS(errno()))
} else {
Ok(max_priority)
}
}
}
}
pub fn min_value_for_policy(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
match policy {
#[cfg(any(target_os = "linux", target_os = "android"))]
ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle) => Ok(0),
ThreadSchedulePolicy::Normal(_) => {
Ok(NICENESS_MIN as libc::c_int)
}
_ => {
let min_priority = unsafe { libc::sched_get_priority_min(policy.to_posix()) };
if min_priority < 0 {
Err(Error::OS(errno()))
} else {
Ok(min_priority)
}
}
}
}
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)
}
ThreadSchedulePolicy::Normal(_) => {
let niceness_values = NICENESS_MAX.abs() + NICENESS_MIN.abs();
let ratio = p as f32 / ThreadPriorityValue::MAX 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 {
runtime: _,
deadline: _,
period: _,
} => 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) = match priority {
ThreadPriority::Deadline {
runtime,
deadline,
period,
} => (|| {
Ok((
runtime.as_nanos().try_into()?,
deadline.as_nanos().try_into()?,
period.as_nanos().try_into()?,
))
})()
.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,
..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 let ThreadSchedulePolicy::Realtime(_) = policy {
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 {
set_errno(0);
unsafe { libc::nice(fixed_priority) };
match errno() {
0 => Ok(()),
e => Err(Error::OS(e)),
}
}
}
}
}
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) -> ThreadId {
thread_native_id()
}
}
impl ThreadExt for std::thread::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]
#[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),
},
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline)
)
.is_ok());
unsafe {
let mut sched_attr = SchedAttr::default();
let ret = libc::syscall(
libc::SYS_sched_getattr,
0, &mut sched_attr as *mut _,
std::mem::size_of::<SchedAttr>() as u32,
0, );
assert!(ret >= 0);
assert_eq!(
sched_attr.sched_policy,
RealtimeThreadSchedulePolicy::Deadline.to_posix() as u32
);
assert_eq!(sched_attr.sched_runtime, 1 * 10_u64.pow(6));
assert_eq!(sched_attr.sched_deadline, 10 * 10_u64.pow(6));
assert_eq!(sched_attr.sched_period, 100 * 10_u64.pow(6));
}
}
}