thread_priority/
unix.rs

1//! This module defines the unix thread control.
2//!
3//! The crate's prelude doesn't have much control over
4//! the unix threads, and this module provides
5//! better control over those.
6
7use std::convert::TryFrom;
8
9#[cfg(target_os = "android")]
10use libc::SCHED_NORMAL as SCHED_OTHER;
11#[cfg(not(target_os = "android"))]
12use libc::SCHED_OTHER;
13#[cfg(target_os = "vxworks")]
14use libc::SCHED_SPORADIC;
15#[cfg(any(target_os = "linux", target_os = "android"))]
16use libc::{SCHED_BATCH, SCHED_IDLE};
17use libc::{SCHED_FIFO, SCHED_RR};
18
19use crate::{Error, ThreadPriority, ThreadPriorityValue};
20use std::mem::MaybeUninit;
21
22// Processes scheduled under one of the real-time policies
23// (SCHED_FIFO, SCHED_RR) have a sched_priority value in the range 1
24// (low) to 99 (high).
25// For threads scheduled under one of the normal scheduling policies
26//  (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), sched_priority is not
27//  used in scheduling decisions (it must be specified as 0).
28// <https://man7.org/linux/man-pages/man7/sched.7.html>
29
30/// An alias type for a thread id.
31pub type ThreadId = libc::pthread_t;
32
33/// The maximum value possible for niceness. Threads with this value
34/// of niceness have the highest priority possible
35pub const NICENESS_MAX: i8 = -20;
36/// The minimum value possible for niceness. Threads with this value
37/// of niceness have the lowest priority possible.
38pub const NICENESS_MIN: i8 = 19;
39
40/// Proxy structure to maintain compatibility between glibc and musl
41#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
42pub struct ScheduleParams {
43    /// Copy of `sched_priority` from `libc::sched_param`
44    pub sched_priority: libc::c_int,
45}
46
47fn errno() -> libc::c_int {
48    unsafe {
49        cfg_if::cfg_if! {
50            if #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "android"))] {
51                *libc::__errno()
52            } else if #[cfg(target_os = "linux")] {
53                *libc::__errno_location()
54            } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] {
55                *libc::__error()
56            } else if #[cfg(target_os = "vxworks")] {
57                libc::errnoGet()
58            } else {
59                compile_error!("Your OS is probably not supported.")
60            }
61        }
62    }
63}
64
65fn set_errno(number: libc::c_int) {
66    unsafe {
67        cfg_if::cfg_if! {
68            if #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "android"))] {
69                *libc::__errno() = number;
70            } else if #[cfg(target_os = "linux")] {
71                *libc::__errno_location() = number;
72            } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] {
73                *libc::__error() = number;
74            } else if #[cfg(target_os = "vxworks")] {
75                let _ = libc::errnoSet(number);
76            } else {
77                compile_error!("Your OS is probably not supported.")
78            }
79        }
80    }
81}
82
83fn do_with_errno<F: FnOnce() -> libc::c_int>(f: F) -> Result<libc::c_int, Error> {
84    let return_value = f();
85    if return_value < 0 {
86        Err(Error::OS(errno()))
87    } else {
88        Ok(return_value)
89    }
90}
91
92/// A copy of the Linux kernel's sched_attr type.
93///
94/// This structure can be used directly with the C api and is
95/// supposed to be fully-compatible.
96#[derive(Debug, Default)]
97#[cfg(any(target_os = "linux", target_os = "android"))]
98#[repr(C)]
99pub struct SchedAttr {
100    size: u32,
101    sched_policy: u32,
102    sched_flags: u64,
103    /// for SCHED_NORMAL and SCHED_BATCH
104    sched_nice: i32,
105    /// for SCHED_FIFO, SCHED_RR
106    sched_priority: u32,
107    /// for SCHED_DEADLINE
108    sched_runtime: u64,
109    /// for SCHED_DEADLINE
110    sched_deadline: u64,
111    /// for SCHED_DEADLINE
112    sched_period: u64,
113    /// Utilization hint
114    sched_util_min: u32,
115    /// Utilization hint
116    sched_util_max: u32,
117}
118
119impl ScheduleParams {
120    fn into_posix(self) -> libc::sched_param {
121        let mut param = unsafe { MaybeUninit::<libc::sched_param>::zeroed().assume_init() };
122        param.sched_priority = self.sched_priority;
123        param
124    }
125
126    fn from_posix(sched_param: libc::sched_param) -> Self {
127        ScheduleParams {
128            sched_priority: sched_param.sched_priority,
129        }
130    }
131}
132
133#[cfg(any(target_os = "linux", target_os = "android"))]
134bitflags::bitflags! {
135    /// Flags for controlling Deadline scheduling behavior.
136    #[repr(transparent)]
137    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
138    pub struct DeadlineFlags: u64 {
139        /// Children created by [`libc::fork`] will not inherit privileged
140        /// scheduling policies.
141        const RESET_ON_FORK = 0x01;
142        /// The thread may reclaim bandwidth that is unused by another
143        /// realtime thread.
144        const RECLAIM = 0x02;
145        /// Allows a task to get informed about runtime overruns through the
146        /// delivery of SIGXCPU signals.
147        const DEADLINE_OVERRUN = 0x04;
148    }
149}
150
151/// Returns scheduling attributes for the current thread.
152#[cfg(any(target_os = "linux", target_os = "android"))]
153pub fn get_thread_scheduling_attributes() -> Result<SchedAttr, Error> {
154    let mut sched_attr = SchedAttr::default();
155    let current_thread = 0;
156    let flags = 0;
157    let ret = unsafe {
158        libc::syscall(
159            libc::SYS_sched_getattr,
160            current_thread,
161            &mut sched_attr as *mut _,
162            std::mem::size_of::<SchedAttr>() as u32,
163            flags,
164        )
165    };
166    if ret < 0 {
167        return Err(Error::OS(errno()));
168    }
169    Ok(sched_attr)
170}
171
172/// The following "real-time" policies are also supported, for special time-critical applications
173/// that need precise control over the way in which runnable processes are selected for execution
174#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
175pub enum RealtimeThreadSchedulePolicy {
176    /// A first-in, first-out policy
177    Fifo,
178    /// A round-robin policy
179    RoundRobin,
180    // Policy similar to Fifo
181    /// A sporadic scheduling policy specific to VxWorks.
182    #[cfg(target_os = "vxworks")]
183    Sporadic,
184    /// A deadline policy. Note, due to Linux expecting a pid_t and not a pthread_t, the given
185    /// [ThreadId](struct.ThreadId) will be interpreted as a pid_t. This policy is NOT
186    /// POSIX-compatible, so we only include it for linux targets.
187    #[cfg(all(
188        any(target_os = "linux", target_os = "android"),
189        not(target_arch = "wasm32")
190    ))]
191    Deadline,
192}
193
194impl RealtimeThreadSchedulePolicy {
195    fn to_posix(self) -> libc::c_int {
196        match self {
197            RealtimeThreadSchedulePolicy::Fifo => SCHED_FIFO,
198            RealtimeThreadSchedulePolicy::RoundRobin => SCHED_RR,
199            #[cfg(target_os = "vxworks")]
200            RealtimeThreadSchedulePolicy::Sporadic => SCHED_SPORADIC,
201            #[cfg(all(
202                any(target_os = "linux", target_os = "android"),
203                not(target_arch = "wasm32")
204            ))]
205            RealtimeThreadSchedulePolicy::Deadline => 6,
206        }
207    }
208}
209
210/// Normal (non-realtime) schedule policies
211/// For these schedule policies, [`niceness`](https://man7.org/linux/man-pages/man7/sched.7.html)
212/// is used.
213#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
214pub enum NormalThreadSchedulePolicy {
215    /// For running very low priority background jobs.
216    /// (Since Linux 2.6.23.) `SCHED_IDLE` can be used only at static priority 0;
217    /// the process nice value has no influence for this policy.
218    ///
219    /// This policy is intended for running jobs at extremely low priority (lower even
220    /// than a +19 nice value with the SCHED_OTHER or SCHED_BATCH policies).
221    #[cfg(any(target_os = "linux", target_os = "android"))]
222    Idle,
223    /// For "batch" style execution of processes.
224    /// (Since Linux 2.6.16.) `SCHED_BATCH` can be used only at static priority 0.
225    /// This policy is similar to SCHED_OTHER in that it schedules the thread
226    /// according to its dynamic priority (based on the nice value). The difference is
227    /// that this policy will cause the scheduler to always assume that the thread is
228    /// CPU-intensive. Consequently, the scheduler will apply a small scheduling penalty
229    /// with respect to wakeup behavior, so that this thread is mildly disfavored in scheduling decisions.
230    ///
231    /// This policy is useful for workloads that are noninteractive, but do not want to lower their
232    /// nice value, and for workloads that want a deterministic scheduling policy without interactivity
233    /// causing extra preemptions (between the workload's tasks).
234    #[cfg(any(target_os = "linux", target_os = "android"))]
235    Batch,
236    /// The standard round-robin time-sharing policy, also sometimes referred to as "Normal".
237    ///
238    /// `SCHED_OTHER` can be used at only static priority 0 (i.e., threads under real-time policies
239    /// always have priority over `SCHED_OTHER` processes). `SCHED_OTHER` is the standard Linux
240    /// time-sharing scheduler that is intended for all threads that do not require the special
241    /// real-time mechanisms.
242    ///
243    /// The thread to run is chosen from the static priority 0 list based on a dynamic priority that
244    /// is determined only inside this list. The dynamic  priority  is based on the nice value (see below)
245    /// and is increased for each time quantum the thread is ready to run, but denied to run by the scheduler.
246    ///
247    /// This ensures fair progress among all `SCHED_OTHER` threads.
248    ///
249    /// In the Linux kernel source code, the `SCHED_OTHER` policy is actually named `SCHED_NORMAL`.
250    Other,
251}
252impl NormalThreadSchedulePolicy {
253    fn to_posix(self) -> libc::c_int {
254        match self {
255            #[cfg(any(target_os = "linux", target_os = "android"))]
256            NormalThreadSchedulePolicy::Idle => SCHED_IDLE,
257            #[cfg(any(target_os = "linux", target_os = "android"))]
258            NormalThreadSchedulePolicy::Batch => SCHED_BATCH,
259            NormalThreadSchedulePolicy::Other => SCHED_OTHER,
260        }
261    }
262}
263
264/// Thread schedule policy definition.
265#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
266pub enum ThreadSchedulePolicy {
267    /// Normal thread schedule policies.
268    Normal(NormalThreadSchedulePolicy),
269    /// Realtime thread schedule policies.
270    Realtime(RealtimeThreadSchedulePolicy),
271}
272impl ThreadSchedulePolicy {
273    fn to_posix(self) -> libc::c_int {
274        match self {
275            ThreadSchedulePolicy::Normal(p) => p.to_posix(),
276            ThreadSchedulePolicy::Realtime(p) => p.to_posix(),
277        }
278    }
279
280    fn from_posix(policy: libc::c_int) -> Result<ThreadSchedulePolicy, Error> {
281        match policy {
282            SCHED_OTHER => Ok(ThreadSchedulePolicy::Normal(
283                NormalThreadSchedulePolicy::Other,
284            )),
285            #[cfg(any(target_os = "linux", target_os = "android"))]
286            SCHED_BATCH => Ok(ThreadSchedulePolicy::Normal(
287                NormalThreadSchedulePolicy::Batch,
288            )),
289            #[cfg(any(target_os = "linux", target_os = "android"))]
290            SCHED_IDLE => Ok(ThreadSchedulePolicy::Normal(
291                NormalThreadSchedulePolicy::Idle,
292            )),
293            SCHED_FIFO => Ok(ThreadSchedulePolicy::Realtime(
294                RealtimeThreadSchedulePolicy::Fifo,
295            )),
296            SCHED_RR => Ok(ThreadSchedulePolicy::Realtime(
297                RealtimeThreadSchedulePolicy::RoundRobin,
298            )),
299            #[cfg(target_os = "vxworks")]
300            SCHED_SPORADIC => Ok(ThreadSchedulePolicy::Realtime(
301                RealtimeThreadSchedulePolicy::Sporadic,
302            )),
303            #[cfg(all(
304                any(target_os = "linux", target_os = "android"),
305                not(target_arch = "wasm32")
306            ))]
307            6 => Ok(ThreadSchedulePolicy::Realtime(
308                RealtimeThreadSchedulePolicy::Deadline,
309            )),
310            _ => Err(Error::Ffi("Can't parse schedule policy from posix")),
311        }
312    }
313}
314
315/// Defines the type of the priority edge value: minimum or maximum.
316#[derive(Debug, Copy, Clone)]
317pub enum PriorityPolicyEdgeValueType {
318    /// Specifies the minimum priority value for a policy.
319    Minimum,
320    /// Specifies the maximum priority value for a policy.
321    Maximum,
322}
323
324impl ThreadPriority {
325    /// Returns the maximum allowed value for using with the provided policy.
326    /// The returned number is in the range of allowed values.
327    pub fn max_value_for_policy(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
328        Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Maximum)
329    }
330
331    /// Returns the minimum allowed value for using with the provided policy.
332    /// The returned number is in the range of allowed values.
333    pub fn min_value_for_policy(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
334        Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Minimum)
335    }
336
337    /// Returns the edge priority for the provided policy.
338    fn get_edge_value_for_policy(
339        policy: ThreadSchedulePolicy,
340        edge: PriorityPolicyEdgeValueType,
341    ) -> Result<libc::c_int, Error> {
342        let get_edge_priority = match edge {
343            PriorityPolicyEdgeValueType::Minimum => Self::get_min_priority,
344            PriorityPolicyEdgeValueType::Maximum => Self::get_max_priority,
345        };
346
347        match policy {
348            #[cfg_attr(
349                not(any(target_os = "linux", target_os = "android")),
350                allow(unused_variables)
351            )]
352            ThreadSchedulePolicy::Normal(normal) => {
353                cfg_if::cfg_if! {
354                    if #[cfg(any(target_os = "linux", target_os = "android"))] {
355                        if normal == NormalThreadSchedulePolicy::Idle {
356                            // Only `0` can be returned for `Idle` threads on Linux/Android.
357                            Ok(0)
358                        } else {
359                            // Niceness can be used on Linux/Android.
360                            Ok(match edge {
361                                PriorityPolicyEdgeValueType::Minimum => NICENESS_MIN as libc::c_int,
362                                PriorityPolicyEdgeValueType::Maximum => NICENESS_MAX as libc::c_int,
363                            })
364                        }
365                    } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "vxworks"))] {
366                        // macOS/iOS and VxWorks allow specifying the priority using sched params.
367                        get_edge_priority(policy)
368                    } else {
369                        Err(Error::Priority(
370                            "Unsupported thread priority for this OS. Change the scheduling policy or use a supported OS.",
371                        ))
372                    }
373                }
374            }
375            _ => get_edge_priority(policy),
376        }
377    }
378
379    /// Returns the maximum scheduling priority for the POSIX policy.
380    fn get_max_priority(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
381        do_with_errno(|| unsafe { libc::sched_get_priority_max(policy.to_posix()) })
382    }
383
384    /// Returns the minimum scheduling priority for the POSIX policy.
385    fn get_min_priority(policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
386        do_with_errno(|| unsafe { libc::sched_get_priority_min(policy.to_posix()) })
387    }
388
389    /// Checks that the passed priority value is within the range of allowed values for using with the provided policy.
390    pub fn to_allowed_value_for_policy(
391        priority: libc::c_int,
392        policy: ThreadSchedulePolicy,
393    ) -> Result<libc::c_int, Error> {
394        let min_priority = Self::min_value_for_policy(policy)?;
395        let max_priority = Self::max_value_for_policy(policy)?;
396        let (min, max) = (
397            std::cmp::min(min_priority, max_priority),
398            std::cmp::max(min_priority, max_priority),
399        );
400        let allowed_range = min..=max;
401        if allowed_range.contains(&priority) {
402            Ok(priority)
403        } else {
404            Err(Error::PriorityNotInRange(allowed_range))
405        }
406    }
407
408    /// Converts the priority stored to a posix number.
409    /// POSIX value can not be known without knowing the scheduling policy
410    /// <https://linux.die.net/man/2/sched_get_priority_max>
411    ///
412    /// 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).
413    /// Source: <https://man7.org/linux/man-pages/man7/sched.7.html>
414    /// Due to this restriction of normal scheduling policies and the intention of the library, the niceness is used
415    /// instead for such processes.
416    pub fn to_posix(self, policy: ThreadSchedulePolicy) -> Result<libc::c_int, Error> {
417        let ret = match self {
418            ThreadPriority::Min => match policy {
419                // SCHED_DEADLINE doesn't really have a notion of priority, this is an error
420                #[cfg(all(
421                    any(target_os = "linux", target_os = "android"),
422                    not(target_arch = "wasm32")
423                ))]
424                ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
425                    Error::Priority("Deadline scheduling must use deadline priority."),
426                ),
427                _ => Self::min_value_for_policy(policy).map(|v| v as u32),
428            },
429            ThreadPriority::Crossplatform(ThreadPriorityValue(p)) => match policy {
430                // SCHED_DEADLINE doesn't really have a notion of priority, this is an error
431                #[cfg(all(
432                    any(target_os = "linux", target_os = "android"),
433                    not(target_arch = "wasm32")
434                ))]
435                ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
436                    Error::Priority("Deadline scheduling must use deadline priority."),
437                ),
438                ThreadSchedulePolicy::Realtime(_) => {
439                    Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32)
440                }
441                // XNU and the derivatives allow to change the priority
442                // for the SCHED_OTHER policy.
443                // <https://www.usenix.org/legacy/publications/library/proceedings/bsdcon02/full_papers/gerbarg/gerbarg_html/index.html>
444                #[cfg(all(
445                    any(target_os = "macos", target_os = "ios", target_os = "vxworks"),
446                    not(target_arch = "wasm32")
447                ))]
448                ThreadSchedulePolicy::Normal(_) => {
449                    Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32)
450                }
451                #[cfg(not(all(
452                    any(target_os = "macos", target_os = "ios", target_os = "vxworks"),
453                    not(target_arch = "wasm32")
454                )))]
455                ThreadSchedulePolicy::Normal(_) => {
456                    // Mapping a [0..100] priority into niceness [-20..20] needs reversing the ratio,
457                    // as the lowest nice is actually the highest priority.
458                    let niceness_values = NICENESS_MAX.abs() + NICENESS_MIN.abs();
459                    let ratio = 1f32 - (p as f32 / ThreadPriorityValue::MAX as f32);
460                    let niceness = ((niceness_values as f32 * ratio) as i8 + NICENESS_MAX) as i32;
461                    Self::to_allowed_value_for_policy(niceness, policy).map(|v| v as u32)
462                }
463            },
464            // TODO avoid code duplication.
465            ThreadPriority::Os(crate::ThreadPriorityOsValue(p)) => match policy {
466                // SCHED_DEADLINE doesn't really have a notion of priority, this is an error
467                #[cfg(all(
468                    any(target_os = "linux", target_os = "android"),
469                    not(target_arch = "wasm32")
470                ))]
471                ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
472                    Error::Priority("Deadline scheduling must use deadline priority."),
473                ),
474                _ => Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32),
475            },
476            ThreadPriority::Max => match policy {
477                // SCHED_DEADLINE doesn't really have a notion of priority, this is an error
478                #[cfg(all(
479                    any(target_os = "linux", target_os = "android"),
480                    not(target_arch = "wasm32")
481                ))]
482                ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err(
483                    Error::Priority("Deadline scheduling must use deadline priority."),
484                ),
485                _ => Self::max_value_for_policy(policy).map(|v| v as u32),
486            },
487            #[cfg(all(
488                any(target_os = "linux", target_os = "android"),
489                not(target_arch = "wasm32")
490            ))]
491            ThreadPriority::Deadline { .. } => Err(Error::Priority(
492                "Deadline is non-POSIX and cannot be converted.",
493            )),
494        };
495        ret.map(|p| p as libc::c_int)
496    }
497
498    /// Gets priority value from POSIX value.
499    /// In order to interpret it correctly, you should also take scheduling policy
500    /// into account.
501    pub fn from_posix(params: ScheduleParams) -> ThreadPriority {
502        ThreadPriority::Crossplatform(ThreadPriorityValue(params.sched_priority as u8))
503    }
504}
505
506#[cfg(any(target_os = "linux", target_os = "android"))]
507fn set_thread_priority_and_policy_deadline(
508    native: ThreadId,
509    priority: ThreadPriority,
510) -> Result<(), Error> {
511    use std::convert::TryInto as _;
512
513    let (runtime, deadline, period, flags) = match priority {
514        ThreadPriority::Deadline {
515            runtime,
516            deadline,
517            period,
518            flags,
519        } => (|| {
520            Ok((
521                runtime.as_nanos().try_into()?,
522                deadline.as_nanos().try_into()?,
523                period.as_nanos().try_into()?,
524                flags,
525            ))
526        })()
527        .map_err(|_: std::num::TryFromIntError| {
528            Error::Priority("Deadline policy durations don't fit into a `u64`.")
529        })?,
530        _ => {
531            return Err(Error::Priority(
532                "Deadline policy given without deadline priority.",
533            ));
534        }
535    };
536    let tid = native as libc::pid_t;
537    let sched_attr = SchedAttr {
538        size: std::mem::size_of::<SchedAttr>() as u32,
539        sched_policy: RealtimeThreadSchedulePolicy::Deadline.to_posix() as u32,
540        sched_runtime: runtime,
541        sched_deadline: deadline,
542        sched_period: period,
543        sched_flags: flags.bits(),
544        ..Default::default()
545    };
546    let ret =
547        unsafe { libc::syscall(libc::SYS_sched_setattr, tid, &sched_attr as *const _, 0) as i32 };
548
549    match ret {
550        0 => Ok(()),
551        e => Err(Error::OS(e)),
552    }
553}
554
555/// Sets thread's priority and schedule policy
556///
557/// * May require privileges
558///
559/// # Usage
560///
561/// Setting thread priority to minimum with normal schedule policy:
562///
563/// ```rust
564/// use thread_priority::*;
565///
566/// let thread_id = thread_native_id();
567/// assert!(set_thread_priority_and_policy(thread_id,
568///                                        ThreadPriority::Min,
569///                                        ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo)).is_ok());
570/// ```
571///
572/// # Note
573///
574/// In case the value is specified as [`ThreadPriority::Crossplatform`] and is incompatible with the policy, an error is returned.
575/// However if [`ThreadPriority::Min`] or [`ThreadPriority::Max`] are used, the correct value is used automatically according
576/// to the range of the policy's allowed values.
577pub fn set_thread_priority_and_policy(
578    native: ThreadId,
579    priority: ThreadPriority,
580    policy: ThreadSchedulePolicy,
581) -> Result<(), Error> {
582    match policy {
583        // SCHED_DEADLINE policy requires its own syscall
584        #[cfg(any(target_os = "linux", target_os = "android"))]
585        ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => {
586            set_thread_priority_and_policy_deadline(native, priority)
587        }
588        _ => {
589            let fixed_priority = priority.to_posix(policy)?;
590            // On VxWorks, macOS and iOS it is possible to set the priority
591            // this way.
592            if matches!(policy, ThreadSchedulePolicy::Realtime(_))
593                || cfg!(any(
594                    target_os = "macos",
595                    target_os = "ios",
596                    target_os = "vxworks"
597                ))
598            {
599                // If the policy is a realtime one, the priority is set via
600                // pthread_setschedparam.
601                let params = ScheduleParams {
602                    sched_priority: fixed_priority,
603                }
604                .into_posix();
605
606                let ret = unsafe {
607                    libc::pthread_setschedparam(
608                        native,
609                        policy.to_posix(),
610                        &params as *const libc::sched_param,
611                    )
612                };
613
614                match ret {
615                    0 => Ok(()),
616                    e => Err(Error::OS(e)),
617                }
618            } else {
619                //VxWorks does not have set priority function
620                #[cfg(target_os = "vxworks")]
621                unsafe fn setpriority(
622                    _which: u32,
623                    _who: u32,
624                    _priority: libc::c_int,
625                ) -> libc::c_int {
626                    set_errno(libc::ENOSYS);
627                    -1
628                }
629
630                #[cfg(not(target_os = "vxworks"))]
631                use libc::setpriority;
632
633                // Normal priority threads must be set with static priority 0.
634                let params = ScheduleParams { sched_priority: 0 }.into_posix();
635
636                let ret = unsafe {
637                    libc::pthread_setschedparam(
638                        native,
639                        policy.to_posix(),
640                        &params as *const libc::sched_param,
641                    )
642                };
643
644                if ret != 0 {
645                    return Err(Error::OS(ret));
646                }
647
648                // Normal priority threads adjust relative priority through niceness.
649                set_errno(0);
650                let ret = unsafe { setpriority(libc::PRIO_PROCESS, 0, fixed_priority) };
651                if ret != 0 {
652                    return Err(Error::OS(errno()));
653                }
654
655                Ok(())
656            }
657        }
658    }
659}
660
661/// Set current thread's priority.
662/// In order to properly map a value of the thread priority, the thread scheduling
663/// must be known. This function attempts to retrieve the current thread's
664/// scheduling policy and thus map the priority value correctly, so that it fits
665/// within the scheduling policy's allowed range of values.
666///
667/// * May require privileges
668///
669/// ```rust
670/// use thread_priority::*;
671///
672/// let thread_id = thread_native_id();
673/// assert!(set_current_thread_priority(ThreadPriority::Min).is_ok());
674/// ```
675pub fn set_current_thread_priority(priority: ThreadPriority) -> Result<(), Error> {
676    let thread_id = thread_native_id();
677    let policy = thread_schedule_policy()?;
678    set_thread_priority_and_policy(thread_id, priority, policy)
679}
680
681/// Returns policy parameters (schedule policy and other schedule parameters) for current process
682///
683/// # Usage
684///
685/// ```rust
686/// use thread_priority::*;
687///
688/// assert!(thread_schedule_policy().is_ok());
689/// ```
690pub fn thread_schedule_policy() -> Result<ThreadSchedulePolicy, Error> {
691    thread_schedule_policy_param(thread_native_id()).map(|policy| policy.0)
692}
693
694/// Returns policy parameters (schedule policy and other schedule parameters)
695///
696/// # Usage
697///
698/// ```rust
699/// use thread_priority::*;
700///
701/// let thread_id = thread_native_id();
702/// assert!(thread_schedule_policy_param(thread_id).is_ok());
703/// ```
704pub fn thread_schedule_policy_param(
705    native: ThreadId,
706) -> Result<(ThreadSchedulePolicy, ScheduleParams), Error> {
707    unsafe {
708        let mut policy = 0i32;
709        let mut params = ScheduleParams { sched_priority: 0 }.into_posix();
710
711        let ret = libc::pthread_getschedparam(
712            native,
713            &mut policy as *mut libc::c_int,
714            &mut params as *mut libc::sched_param,
715        );
716        match ret {
717            0 => Ok((
718                ThreadSchedulePolicy::from_posix(policy)?,
719                ScheduleParams::from_posix(params),
720            )),
721            e => Err(Error::OS(e)),
722        }
723    }
724}
725
726/// Get the thread's priority value.
727pub fn get_thread_priority(native: ThreadId) -> Result<ThreadPriority, Error> {
728    Ok(ThreadPriority::from_posix(
729        thread_schedule_policy_param(native)?.1,
730    ))
731}
732
733/// Get current thread's priority value.
734pub fn get_current_thread_priority() -> Result<ThreadPriority, Error> {
735    get_thread_priority(thread_native_id())
736}
737
738/// A helper trait for other threads to implement to be able to call methods
739/// on threads themselves.
740///
741/// ```rust
742/// use thread_priority::*;
743///
744/// assert!(std::thread::current().get_priority().is_ok());
745///
746/// let join_handle = std::thread::spawn(|| println!("Hello world!"));
747/// assert!(join_handle.thread().get_priority().is_ok());
748///
749/// join_handle.join();
750/// ```
751pub trait ThreadExt {
752    /// Gets the current thread's priority.
753    /// For more info read [`get_current_thread_priority`].
754    ///
755    /// ```rust
756    /// use thread_priority::*;
757    ///
758    /// assert!(std::thread::current().get_priority().is_ok());
759    /// ```
760    fn get_priority(&self) -> Result<ThreadPriority, Error> {
761        get_current_thread_priority()
762    }
763
764    /// Sets the current thread's priority.
765    /// For more info see [`ThreadPriority::set_for_current`].
766    ///
767    /// ```rust
768    /// use thread_priority::*;
769    ///
770    /// assert!(std::thread::current().set_priority(ThreadPriority::Min).is_ok());
771    /// ```
772    fn set_priority(&self, priority: ThreadPriority) -> Result<(), Error> {
773        priority.set_for_current()
774    }
775
776    /// Gets the current thread's schedule policy.
777    /// For more info read [`thread_schedule_policy`].
778    fn get_schedule_policy(&self) -> Result<ThreadSchedulePolicy, Error> {
779        thread_schedule_policy()
780    }
781
782    /// Returns current thread's schedule policy and parameters.
783    /// For more info read [`thread_schedule_policy_param`].
784    fn get_schedule_policy_param(&self) -> Result<(ThreadSchedulePolicy, ScheduleParams), Error> {
785        thread_schedule_policy_param(thread_native_id())
786    }
787
788    /// Sets current thread's schedule policy.
789    /// For more info read [`set_thread_priority_and_policy`].
790    fn set_priority_and_policy(
791        &self,
792        policy: ThreadSchedulePolicy,
793        priority: ThreadPriority,
794    ) -> Result<(), Error> {
795        cfg_if::cfg_if! {
796            if #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_arch = "wasm32")))] {
797                if policy == ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) {
798                    set_thread_priority_and_policy(thread_native_id(), ThreadPriority::Crossplatform(ThreadPriorityValue(0)), policy)
799                } else {
800                    set_thread_priority_and_policy(thread_native_id(), priority, policy)
801                }
802            } else {
803                set_thread_priority_and_policy(thread_native_id(), priority, policy)
804            }
805        }
806    }
807
808    /// Returns native unix thread id.
809    /// For more info read [`thread_native_id`].
810    ///
811    /// ```rust
812    /// use thread_priority::*;
813    ///
814    /// assert!(std::thread::current().get_native_id().unwrap() > 0);
815    fn get_native_id(&self) -> Result<ThreadId, Error>;
816}
817
818/// Auto-implementation of this trait for the [`std::thread::Thread`].
819impl ThreadExt for std::thread::Thread {
820    fn get_native_id(&self) -> Result<ThreadId, Error> {
821        if self.id() == std::thread::current().id() {
822            Ok(thread_native_id())
823        } else {
824            Err(Error::Priority(
825                "The `ThreadExt::get_native_id()` is currently limited to be called on the current thread.",
826            ))
827        }
828    }
829}
830
831/// Returns current thread id, which is the current OS's native handle.
832/// It may or may not be equal or even related to rust's thread id,
833/// there is absolutely no guarantee for that.
834///
835/// # Usage
836///
837/// ```rust
838/// use thread_priority::thread_native_id;
839///
840/// assert!(thread_native_id() > 0);
841/// ```
842pub fn thread_native_id() -> ThreadId {
843    unsafe { libc::pthread_self() }
844}
845
846impl TryFrom<u8> for ThreadPriority {
847    type Error = &'static str;
848
849    fn try_from(value: u8) -> Result<Self, Self::Error> {
850        if let 0..=100 = value {
851            Ok(ThreadPriority::Crossplatform(ThreadPriorityValue(value)))
852        } else {
853            Err("The thread priority value must be in range of [0; 100].")
854        }
855    }
856}
857
858#[cfg(test)]
859mod tests {
860    use crate::unix::*;
861
862    #[test]
863    fn thread_schedule_policy_param_test() {
864        let thread_id = thread_native_id();
865
866        assert!(thread_schedule_policy_param(thread_id).is_ok());
867    }
868
869    // Running this test requires CAP_SYS_NICE.
870    #[test]
871    fn change_between_realtime_and_normal_policies_requires_capabilities() {
872        use crate::ThreadPriorityOsValue;
873
874        const TEST_PRIORITY: u8 = 15;
875
876        let realtime_policy = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo);
877        let normal_policy = ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other);
878
879        // While we may desire an OS-specific priority, the reported value is always crossplatform.
880        let desired_priority = ThreadPriority::Os(ThreadPriorityOsValue(TEST_PRIORITY as _));
881        let expected_priority = ThreadPriority::Crossplatform(ThreadPriorityValue(TEST_PRIORITY));
882
883        let thread = std::thread::current();
884        thread
885            .set_priority_and_policy(realtime_policy, desired_priority)
886            .expect("to set realtime fifo policy");
887
888        assert_eq!(thread.get_schedule_policy(), Ok(realtime_policy));
889        assert_eq!(thread.get_priority(), Ok(expected_priority));
890
891        thread
892            .set_priority_and_policy(normal_policy, desired_priority)
893            .expect("to set normal other policy");
894
895        assert_eq!(thread.get_schedule_policy(), Ok(normal_policy));
896
897        // On linux, normal priority threads always have static priority 0. Instead the "nice" value is used.
898        #[cfg(not(target_os = "linux"))]
899        assert_eq!(thread.get_priority(), Ok(expected_priority));
900        #[cfg(target_os = "linux")]
901        {
902            let nice = unsafe { libc::getpriority(0, 0) };
903            assert_eq!(nice, TEST_PRIORITY as i32);
904        }
905    }
906
907    #[test]
908    #[cfg(target_os = "linux")]
909    fn set_deadline_policy() {
910        // allow the identity operation for clarity
911        #![allow(clippy::identity_op)]
912        use std::time::Duration;
913
914        assert!(
915            set_thread_priority_and_policy(
916                0, // current thread
917                ThreadPriority::Deadline {
918                    runtime: Duration::from_millis(1),
919                    deadline: Duration::from_millis(10),
920                    period: Duration::from_millis(100),
921                    flags: DeadlineFlags::RESET_ON_FORK,
922                },
923                ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline)
924            )
925            .is_ok()
926        );
927
928        let attributes = get_thread_scheduling_attributes().unwrap();
929        assert_eq!(
930            attributes.sched_policy,
931            RealtimeThreadSchedulePolicy::Deadline.to_posix() as u32
932        );
933        assert_eq!(attributes.sched_runtime, 1 * 10_u64.pow(6));
934        assert_eq!(attributes.sched_deadline, 10 * 10_u64.pow(6));
935        assert_eq!(attributes.sched_period, 100 * 10_u64.pow(6));
936        assert_eq!(attributes.sched_flags, DeadlineFlags::RESET_ON_FORK.bits());
937    }
938}