From e9670180d2ad076fbffcf6eecd621ee5cbf35936 Mon Sep 17 00:00:00 2001 From: B I Mohammed Abbas Date: Fri, 18 Oct 2024 17:56:01 +0530 Subject: [PATCH] Add VxWorks support in unix --- src/unix.rs | 28 +- src/vxworks.rs | 906 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 929 insertions(+), 5 deletions(-) create mode 100644 src/vxworks.rs diff --git a/src/unix.rs b/src/unix.rs index f6aad7f..1eac893 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -12,6 +12,8 @@ use libc::SCHED_NORMAL as SCHED_OTHER; use libc::SCHED_OTHER; #[cfg(any(target_os = "linux", target_os = "android"))] use libc::{SCHED_BATCH, SCHED_IDLE}; +#[cfg(target_os = "vxworks")] +use libc::SCHED_SPORADIC; use libc::{SCHED_FIFO, SCHED_RR}; use crate::{Error, ThreadPriority, ThreadPriorityValue}; @@ -51,6 +53,8 @@ fn errno() -> libc::c_int { *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.") } @@ -67,6 +71,8 @@ fn set_errno(number: libc::c_int) { *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.") } @@ -171,6 +177,9 @@ pub enum RealtimeThreadSchedulePolicy { Fifo, /// A round-robin policy RoundRobin, + // Policy similar to Fifo + #[cfg(target_os = "vxworks")] + Sporadic, /// A deadline policy. Note, due to Linux expecting a pid_t and not a pthread_t, the given /// [ThreadId](struct.ThreadId) will be interpreted as a pid_t. This policy is NOT /// POSIX-compatible, so we only include it for linux targets. @@ -186,6 +195,8 @@ impl RealtimeThreadSchedulePolicy { 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") @@ -284,6 +295,10 @@ impl ThreadSchedulePolicy { 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") @@ -346,8 +361,8 @@ impl ThreadPriority { PriorityPolicyEdgeValueType::Maximum => NICENESS_MAX as libc::c_int, }) } - } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { - // macOS/iOS allows specifying the priority using sched params. + } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "vxworks"))] { + // macOS/iOS and VxWorks allow specifying the priority using sched params. get_edge_priority(policy) } else { Err(Error::Priority( @@ -426,7 +441,7 @@ impl ThreadPriority { // for the SCHED_OTHER policy. // #[cfg(all( - any(target_os = "macos", target_os = "ios"), + any(target_os = "macos", target_os = "ios", target_os = "vxworks"), not(target_arch = "wasm32") ))] ThreadSchedulePolicy::Normal(_) => { @@ -571,10 +586,10 @@ pub fn set_thread_priority_and_policy( } _ => { let fixed_priority = priority.to_posix(policy)?; - // On macOS and iOS it is possible to set the priority + // On VxWorks, macOS and iOS it is possible to set the priority // this way. if matches!(policy, ThreadSchedulePolicy::Realtime(_)) - || cfg!(any(target_os = "macos", target_os = "ios")) + || cfg!(any(target_os = "macos", target_os = "ios", target_os = "vxworks")) { // If the policy is a realtime one, the priority is set via // pthread_setschedparam. @@ -613,6 +628,9 @@ pub fn set_thread_priority_and_policy( // Normal priority threads adjust relative priority through niceness. set_errno(0); + // VxWorks does not have setpriority function call. Control never + // reaches this statement because of the cfg condition in if + #[cfg(not(target_os = "vxworks"))] let ret = unsafe { libc::setpriority(libc::PRIO_PROCESS, 0, fixed_priority) }; if ret != 0 { return Err(Error::OS(errno())); diff --git a/src/vxworks.rs b/src/vxworks.rs new file mode 100644 index 0000000..2fe2758 --- /dev/null +++ b/src/vxworks.rs @@ -0,0 +1,906 @@ +//! This module defines the unix thread control. +//! +//! The crate's prelude doesn't have much control over +//! the unix threads, and this module provides +//! better control over those. + +use std::convert::TryFrom; + +#[cfg(target_os = "android")] +use libc::SCHED_NORMAL as SCHED_OTHER; +#[cfg(not(any(target_os = "android", target_os = "vxworks")))] +use libc::SCHED_OTHER; +#[cfg(any(target_os = "linux", target_os = "android"))] +use libc::{SCHED_BATCH, SCHED_IDLE}; +#[cfg(not(target_os = "vxworks"))] +use libc::{SCHED_FIFO, SCHED_RR}; + +pub const SCHED_FIFO: libc::c_int = 0x01; +pub const SCHED_RR: libc::c_int = 0x02; +pub const SCHED_OTHER: libc::c_int = 0x04; +pub const SCHED_SPORADIC: libc::c_int = 0x08; +pub const PRIO_PROCESS: u32 = 0; + +extern "C"{ + pub fn sched_get_priority_max(policy: libc::c_int) -> libc::c_int; + pub fn sched_get_priority_min(policy: libc::c_int) -> libc::c_int; + pub fn pthread_setschedparam( + native: libc::pthread_t, + policy: libc::c_int, + param: *const libc::_Sched_param, + ) -> libc::c_int; + pub fn pthread_getschedparam( + native: libc::pthread_t, + policy: *mut libc::c_int, + param: *mut libc::_Sched_param, + ) -> libc::c_int; + //pub fn setpriority(which: u32, who: u32, prio: i32) -> libc::c_int; +} + +use crate::{Error, ThreadPriority, ThreadPriorityValue}; +use std::mem::MaybeUninit; + +// Processes scheduled under one of the real-time policies +// (SCHED_FIFO, SCHED_RR) have a sched_priority value in the range 1 +// (low) to 99 (high). +// For threads scheduled under one of the normal scheduling policies +// (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), sched_priority is not +// used in scheduling decisions (it must be specified as 0). +// + +/// An alias type for a thread id. +pub type ThreadId = libc::pthread_t; + +/// The maximum value possible for niceness. Threads with this value +/// of niceness have the highest priority possible +pub const NICENESS_MAX: i8 = -20; +/// The minimum value possible for niceness. Threads with this value +/// of niceness have the lowest priority possible. +pub const NICENESS_MIN: i8 = 19; + +/// Proxy structure to maintain compatibility between glibc and musl +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct ScheduleParams { + /// Copy of `sched_priority` from `libc::_Sched_param` + 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 libc::c_int>(f: F) -> Result { + let return_value = f(); + if return_value < 0 { + Err(Error::OS(errno())) + } else { + Ok(return_value) + } +} + +/// A copy of the Linux kernel's sched_attr type. +/// +/// This structure can be used directly with the C api and is +/// supposed to be fully-compatible. +#[derive(Debug, Default)] +#[cfg(any(target_os = "linux", target_os = "android"))] +#[repr(C)] +pub struct SchedAttr { + size: u32, + sched_policy: u32, + sched_flags: u64, + /// for SCHED_NORMAL and SCHED_BATCH + sched_nice: i32, + /// for SCHED_FIFO, SCHED_RR + sched_priority: u32, + /// for SCHED_DEADLINE + sched_runtime: u64, + /// for SCHED_DEADLINE + sched_deadline: u64, + /// for SCHED_DEADLINE + sched_period: u64, + /// Utilization hint + sched_util_min: u32, + /// Utilization hint + sched_util_max: u32, +} + +impl ScheduleParams { + fn into_posix(self) -> libc::_Sched_param { + let mut param = unsafe { MaybeUninit::::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! { + /// Flags for controlling Deadline scheduling behavior. + #[repr(transparent)] + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct DeadlineFlags: u64 { + /// Children created by [`libc::fork`] will not inherit privileged + /// scheduling policies. + const RESET_ON_FORK = 0x01; + /// The thread may reclaim bandwidth that is unused by another + /// realtime thread. + const RECLAIM = 0x02; + /// Allows a task to get informed about runtime overruns through the + /// delivery of SIGXCPU signals. + const DEADLINE_OVERRUN = 0x04; + } +} + +/// Returns scheduling attributes for the current thread. +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn get_thread_scheduling_attributes() -> Result { + 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::() as u32, + flags, + ) + }; + if ret < 0 { + return Err(Error::OS(errno())); + } + Ok(sched_attr) +} + +/// The following "real-time" policies are also supported, for special time-critical applications +/// that need precise control over the way in which runnable processes are selected for execution +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum RealtimeThreadSchedulePolicy { + /// A first-in, first-out policy + Fifo, + /// A round-robin policy + RoundRobin, + /// A deadline policy. Note, due to Linux expecting a pid_t and not a pthread_t, the given + /// [ThreadId](struct.ThreadId) will be interpreted as a pid_t. This policy is NOT + /// POSIX-compatible, so we only include it for linux targets. + #[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, + } + } +} + +/// Normal (non-realtime) schedule policies +/// For these schedule policies, [`niceness`](https://man7.org/linux/man-pages/man7/sched.7.html) +/// is used. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum NormalThreadSchedulePolicy { + /// For running very low priority background jobs. + /// (Since Linux 2.6.23.) `SCHED_IDLE` can be used only at static priority 0; + /// the process nice value has no influence for this policy. + /// + /// This policy is intended for running jobs at extremely low priority (lower even + /// than a +19 nice value with the SCHED_OTHER or SCHED_BATCH policies). + #[cfg(any(target_os = "linux", target_os = "android"))] + Idle, + /// For "batch" style execution of processes. + /// (Since Linux 2.6.16.) `SCHED_BATCH` can be used only at static priority 0. + /// This policy is similar to SCHED_OTHER in that it schedules the thread + /// according to its dynamic priority (based on the nice value). The difference is + /// that this policy will cause the scheduler to always assume that the thread is + /// CPU-intensive. Consequently, the scheduler will apply a small scheduling penalty + /// with respect to wakeup behavior, so that this thread is mildly disfavored in scheduling decisions. + /// + /// This policy is useful for workloads that are noninteractive, but do not want to lower their + /// nice value, and for workloads that want a deterministic scheduling policy without interactivity + /// causing extra preemptions (between the workload's tasks). + #[cfg(any(target_os = "linux", target_os = "android"))] + Batch, + /// The standard round-robin time-sharing policy, also sometimes referred to as "Normal". + /// + /// `SCHED_OTHER` can be used at only static priority 0 (i.e., threads under real-time policies + /// always have priority over `SCHED_OTHER` processes). `SCHED_OTHER` is the standard Linux + /// time-sharing scheduler that is intended for all threads that do not require the special + /// real-time mechanisms. + /// + /// The thread to run is chosen from the static priority 0 list based on a dynamic priority that + /// is determined only inside this list. The dynamic priority is based on the nice value (see below) + /// and is increased for each time quantum the thread is ready to run, but denied to run by the scheduler. + /// + /// This ensures fair progress among all `SCHED_OTHER` threads. + /// + /// In the Linux kernel source code, the `SCHED_OTHER` policy is actually named `SCHED_NORMAL`. + 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, + } + } +} + +/// Thread schedule policy definition. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum ThreadSchedulePolicy { + /// Normal thread schedule policies. + Normal(NormalThreadSchedulePolicy), + /// Realtime thread schedule policies. + 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 { + 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")), + } + } +} + +/// Defines the type of the priority edge value: minimum or maximum. +#[derive(Debug, Copy, Clone)] +pub enum PriorityPolicyEdgeValueType { + /// Specifies the minimum priority value for a policy. + Minimum, + /// Specifies the maximum priority value for a policy. + Maximum, +} + +impl ThreadPriority { + /// Returns the maximum allowed value for using with the provided policy. + /// The returned number is in the range of allowed values. + pub fn max_value_for_policy(policy: ThreadSchedulePolicy) -> Result { + Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Maximum) + } + + /// Returns the minimum allowed value for using with the provided policy. + /// The returned number is in the range of allowed values. + pub fn min_value_for_policy(policy: ThreadSchedulePolicy) -> Result { + Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Minimum) + } + + /// Returns the edge priority for the provided policy. + fn get_edge_value_for_policy( + policy: ThreadSchedulePolicy, + edge: PriorityPolicyEdgeValueType, + ) -> Result { + 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 { + // Only `0` can be returned for `Idle` threads on Linux/Android. + Ok(0) + } else { + // Niceness can be used on Linux/Android. + 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"))] { + // macOS/iOS allows specifying the priority using sched params. + 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), + } + } + + /// Returns the maximum scheduling priority for the POSIX policy. + fn get_max_priority(policy: ThreadSchedulePolicy) -> Result { + do_with_errno(|| unsafe { sched_get_priority_max(policy.to_posix()) }) + } + + /// Returns the minimum scheduling priority for the POSIX policy. + fn get_min_priority(policy: ThreadSchedulePolicy) -> Result { + do_with_errno(|| unsafe { sched_get_priority_min(policy.to_posix()) }) + } + + /// Checks that the passed priority value is within the range of allowed values for using with the provided policy. + pub fn to_allowed_value_for_policy( + priority: libc::c_int, + policy: ThreadSchedulePolicy, + ) -> Result { + 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)) + } + } + + /// Converts the priority stored to a posix number. + /// POSIX value can not be known without knowing the scheduling policy + /// + /// + /// For threads scheduled under one of the normal scheduling policies (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), sched_priority is not used in scheduling decisions (it must be specified as 0). + /// Source: + /// Due to this restriction of normal scheduling policies and the intention of the library, the niceness is used + /// instead for such processes. + pub fn to_posix(self, policy: ThreadSchedulePolicy) -> Result { + let ret = match self { + ThreadPriority::Min => match policy { + // SCHED_DEADLINE doesn't really have a notion of priority, this is an error + #[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 { + // SCHED_DEADLINE doesn't really have a notion of priority, this is an error + #[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) + } + // XNU and the derivatives allow to change the priority + // for the SCHED_OTHER policy. + // + #[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"), + not(target_arch = "wasm32") + )))] + ThreadSchedulePolicy::Normal(_) => { + // Mapping a [0..100] priority into niceness [-20..20] needs reversing the ratio, + // as the lowest nice is actually the highest priority. + let niceness_values = NICENESS_MAX.abs() + NICENESS_MIN.abs(); + let ratio = 1f32 - (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) + } + }, + // TODO avoid code duplication. + ThreadPriority::Os(crate::ThreadPriorityOsValue(p)) => match policy { + // SCHED_DEADLINE doesn't really have a notion of priority, this is an error + #[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 { + // SCHED_DEADLINE doesn't really have a notion of priority, this is an error + #[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) + } + + /// Gets priority value from POSIX value. + /// In order to interpret it correctly, you should also take scheduling policy + /// into account. + 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::() 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)), + } +} + +/// Sets thread's priority and schedule policy +/// +/// * May require privileges +/// +/// # Usage +/// +/// Setting thread priority to minimum with normal schedule policy: +/// +/// ```rust +/// use thread_priority::*; +/// +/// let thread_id = thread_native_id(); +/// assert!(set_thread_priority_and_policy(thread_id, +/// ThreadPriority::Min, +/// ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo)).is_ok()); +/// ``` +/// +/// # Note +/// +/// In case the value is specified as [`ThreadPriority::Crossplatform`] and is incompatible with the policy, an error is returned. +/// However if [`ThreadPriority::Min`] or [`ThreadPriority::Max`] are used, the correct value is used automatically according +/// to the range of the policy's allowed values. +pub fn set_thread_priority_and_policy( + native: ThreadId, + priority: ThreadPriority, + policy: ThreadSchedulePolicy, +) -> Result<(), Error> { + match policy { + // SCHED_DEADLINE policy requires its own syscall + #[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); + let fixed_priority = fixed_priority?; + // On macOS and iOS it is possible to set the priority + // this way. + if matches!(policy, ThreadSchedulePolicy::Realtime(_)) + || cfg!(any(target_os = "macos", target_os = "ios", target_os = "vxworks")) + { + // If the policy is a realtime one, the priority is set via + // pthread_setschedparam. + let params = ScheduleParams { + sched_priority: fixed_priority, + } + .into_posix(); + + let ret = unsafe { + pthread_setschedparam( + native, + policy.to_posix(), + ¶ms as *const libc::_Sched_param, + ) + }; + + match ret { + 0 => Ok(()), + e => Err(Error::OS(e)), + } + } else { + panic!("unsupported in vxworks"); + } + } + } +} + +/// Set current thread's priority. +/// In order to properly map a value of the thread priority, the thread scheduling +/// must be known. This function attempts to retrieve the current thread's +/// scheduling policy and thus map the priority value correctly, so that it fits +/// within the scheduling policy's allowed range of values. +/// +/// * May require privileges +/// +/// ```rust +/// use thread_priority::*; +/// +/// let thread_id = thread_native_id(); +/// assert!(set_current_thread_priority(ThreadPriority::Min).is_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) +} + +/// Returns policy parameters (schedule policy and other schedule parameters) for current process +/// +/// # Usage +/// +/// ```rust +/// use thread_priority::*; +/// +/// assert!(thread_schedule_policy().is_ok()); +/// ``` +pub fn thread_schedule_policy() -> Result { + thread_schedule_policy_param(thread_native_id()).map(|policy| policy.0) +} + +/// Returns policy parameters (schedule policy and other schedule parameters) +/// +/// # Usage +/// +/// ```rust +/// use thread_priority::*; +/// +/// let thread_id = thread_native_id(); +/// assert!(thread_schedule_policy_param(thread_id).is_ok()); +/// ``` +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 = 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)), + } + } +} + +/// Get the thread's priority value. +pub fn get_thread_priority(native: ThreadId) -> Result { + Ok(ThreadPriority::from_posix( + thread_schedule_policy_param(native)?.1, + )) +} + +/// Get current thread's priority value. +pub fn get_current_thread_priority() -> Result { + get_thread_priority(thread_native_id()) +} + +/// A helper trait for other threads to implement to be able to call methods +/// on threads themselves. +/// +/// ```rust +/// use thread_priority::*; +/// +/// assert!(std::thread::current().get_priority().is_ok()); +/// +/// let join_handle = std::thread::spawn(|| println!("Hello world!")); +/// assert!(join_handle.thread().get_priority().is_ok()); +/// +/// join_handle.join(); +/// ``` +pub trait ThreadExt { + /// Gets the current thread's priority. + /// For more info read [`get_current_thread_priority`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().get_priority().is_ok()); + /// ``` + fn get_priority(&self) -> Result { + get_current_thread_priority() + } + + /// Sets the current thread's priority. + /// For more info see [`ThreadPriority::set_for_current`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().set_priority(ThreadPriority::Min).is_ok()); + /// ``` + fn set_priority(&self, priority: ThreadPriority) -> Result<(), Error> { + priority.set_for_current() + } + + /// Gets the current thread's schedule policy. + /// For more info read [`thread_schedule_policy`]. + fn get_schedule_policy(&self) -> Result { + thread_schedule_policy() + } + + /// Returns current thread's schedule policy and parameters. + /// For more info read [`thread_schedule_policy_param`]. + fn get_schedule_policy_param(&self) -> Result<(ThreadSchedulePolicy, ScheduleParams), Error> { + thread_schedule_policy_param(thread_native_id()) + } + + /// Sets current thread's schedule policy. + /// For more info read [`set_thread_priority_and_policy`]. + 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) + } + } + } + + /// Returns native unix thread id. + /// For more info read [`thread_native_id`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().get_native_id().unwrap() > 0); + fn get_native_id(&self) -> Result; +} + +/// Auto-implementation of this trait for the [`std::thread::Thread`]. +impl ThreadExt for std::thread::Thread { + fn get_native_id(&self) -> Result { + 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.")) + } + } +} + +/// Returns current thread id, which is the current OS's native handle. +/// It may or may not be equal or even related to rust's thread id, +/// there is absolutely no guarantee for that. +/// +/// # Usage +/// +/// ```rust +/// use thread_priority::thread_native_id; +/// +/// assert!(thread_native_id() > 0); +/// ``` +pub fn thread_native_id() -> ThreadId { + unsafe { libc::pthread_self() } +} + +impl TryFrom for ThreadPriority { + type Error = &'static str; + + fn try_from(value: u8) -> Result { + 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()); + } + + // Running this test requires CAP_SYS_NICE. + #[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); + + // While we may desire an OS-specific priority, the reported value is always crossplatform. + 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)); + + // On linux, normal priority threads always have static priority 0. Instead the "nice" value is used. + #[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 the identity operation for clarity + #![allow(clippy::identity_op)] + use std::time::Duration; + + assert!(set_thread_priority_and_policy( + 0, // current thread + 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()); + } +}