diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbf9a8e..51d99c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,8 @@ jobs: - name: Test all features (Linux) if: matrix.features == '' && runner.os == 'Linux' - run: sudo -E /usr/share/rust/.cargo/bin/cargo test --all-features + run: sudo -E /home/runner/.cargo/bin/cargo test --all-features +# run: sudo -E /usr/share/rust/.cargo/bin/cargo test --all-features clippy: name: Run clippy diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index bf385cf..64a4272 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -58,9 +58,14 @@ jobs: if: matrix.features == '' run: cargo build --all-features - - name: Test all features - if: matrix.features == '' - run: cargo test --all-features + - name: Test all features (other) + if: matrix.features == '' && runner.os != 'Linux' + run: cargo test --all-features -- --skip set_deadline_policy + + - name: Test all features (Linux) + if: matrix.features == '' && runner.os == 'Linux' + run: sudo -E /home/runner/.cargo/bin/cargo test --all-features +# run: sudo -E /usr/share/rust/.cargo/bin/cargo test --all-features rustfmt: name: Format diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f1edde6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -language: rust -rust: - - stable - - beta -os: - - linux - - windows -dist: xenial -cache: - cargo: true - timeout: 900 -before_cache: - # Travis can't cache files that are not readable by "others" - - chmod -R a+r $HOME/.cargo -matrix: - allow_failures: - - nightly -before_script: - - export PATH="$PATH:$HOME/.cargo/bin" - - rustup component add rustfmt - - rustup component add clippy -script: - - cargo check - - cargo fmt --verbose --all -- --check - - cargo clippy - - cargo test --doc --verbose $FEATURES -- --nocapture - - cargo test --verbose $FEATURES -- --nocapture -notifications: - email: false diff --git a/Cargo.toml b/Cargo.toml index 20d7f90..fb4e47d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thread-priority" -version = "0.3.0" +version = "0.4.0" authors = ["Victor Polevoy "] description = "Library for managing threads priority and schedule policies" repository = "https://github.com/vityafx/thread-priority" @@ -11,6 +11,9 @@ keywords = ["thread", "schedule", "priority", "pthread"] categories = ["concurrency", "asynchronous", "os"] edition = "2018" +[dependencies] +log = "0.4" + [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/README.md b/README.md index 3974ee5..db85a58 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,9 @@ so feel free to contribute! - Linux - Windows -## Example +## Examples + +### Minimal cross-platform examples Setting current thread's priority to minimum: ```rust,no_run @@ -25,5 +27,94 @@ fn main() { } ``` +The same as above but using a specific value: + +```rust,no_run +use thread_priority::*; +use std::convert::TryInto; + +fn main() { + // The lower the number the lower the priority. + assert!(set_current_thread_priority(ThreadPriority::Crossplatform(0.try_into().unwrap())).is_ok()); +} +``` + +### Windows-specific examples +Set the thread priority to the lowest possible value: + +```rust,no_run +use thread_priority::*; + +fn main() { + // The lower the number the lower the priority. + assert!(set_current_thread_priority(ThreadPriority::Os(WinAPIThreadPriority::Lowest.into())).is_ok()); +} +``` + +Set the ideal processor for the new thread: + +```rust,no_run +use thread_priority::*; + +fn main() { + std::thread::spawn(|| { + set_thread_ideal_processor(thread_native_id(), 0); + println!("Hello world!"); + }); +} +``` + + +### Building a thread using the ThreadBuilderExt trait + +```rust,no_run +use thread_priority::*; +use thread_priority::ThreadBuilderExt; + +let thread = std::thread::Builder::new() + .name("MyNewThread".to_owned()) + .spawn_with_priority(ThreadPriority::Max, |result| { + // This is printed out from within the spawned thread. + println!("Set priority result: {:?}", result); + assert!(result.is_ok()); +}).unwrap(); +thread.join(); +``` + +### Building a thread using the ThreadBuilder. + +```rust,no_run +use thread_priority::*; + +let thread = ThreadBuilder::default() + .name("MyThread") + .priority(ThreadPriority::Max) + .spawn(|result| { + // This is printed out from within the spawned thread. + println!("Set priority result: {:?}", result); + assert!(result.is_ok()); +}).unwrap(); +thread.join(); + +// Another example where we don't care about the priority having been set. +let thread = ThreadBuilder::default() + .name("MyThread") + .priority(ThreadPriority::Max) + .spawn_careless(|| { + // This is printed out from within the spawned thread. + println!("We don't care about the priority result."); +}).unwrap(); +thread.join(); +``` + +### Using ThreadExt trait on the current thread + +```rust,no_run +use thread_priority::*; + +assert!(std::thread::current().get_priority().is_ok()); +println!("This thread's native id is: {:?}", std::thread::current().get_native_id()); +``` + ## License This project is [licensed under the MIT license](https://github.com/vityafx/thread-priority/blob/master/LICENSE). diff --git a/src/lib.rs b/src/lib.rs index 3f91ae2..c7cb303 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,79 @@ //! // Or like this: //! assert!(ThreadPriority::Min.set_for_current().is_ok()); //! ``` +//! +//! # More examples +//! +//! ### Minimal cross-platform examples +//! Setting current thread's priority to minimum: +//! +//! ```rust,no_run +//! use thread_priority::*; +//! +//! assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); +//! ``` +//! +//! The same as above but using a specific value: +//! +//! ```rust,no_run +//! use thread_priority::*; +//! use std::convert::TryInto; +//! +//! // The lower the number the lower the priority. +//! assert!(set_current_thread_priority(ThreadPriority::Crossplatform(0.try_into().unwrap())).is_ok()); +//! ``` +//! +//! ### Building a thread using the [`ThreadBuilderExt`] trait +//! +//! ```rust,no_run +//! use thread_priority::*; +//! use thread_priority::ThreadBuilderExt; +//! +//! let thread = std::thread::Builder::new() +//! .name("MyNewThread".to_owned()) +//! .spawn_with_priority(ThreadPriority::Max, |result| { +//! // This is printed out from within the spawned thread. +//! println!("Set priority result: {:?}", result); +//! assert!(result.is_ok()); +//! }).unwrap(); +//! thread.join(); +//! ``` +//! +//! ### Building a thread using the [`ThreadBuilder`]. +//! +//! ```rust,no_run +//! use thread_priority::*; +//! +//! let thread = ThreadBuilder::default() +//! .name("MyThread") +//! .priority(ThreadPriority::Max) +//! .spawn(|result| { +//! // This is printed out from within the spawned thread. +//! println!("Set priority result: {:?}", result); +//! assert!(result.is_ok()); +//! }).unwrap(); +//! thread.join(); +//! +//! // Another example where we don't care about the priority having been set. +//! let thread = ThreadBuilder::default() +//! .name("MyThread") +//! .priority(ThreadPriority::Max) +//! .spawn_careless(|| { +//! // This is printed out from within the spawned thread. +//! println!("We don't care about the priority result."); +//! }).unwrap(); +//! thread.join(); +//! ``` +//! +//! ### Using [`ThreadExt`] trait on the current thread +//! +//! ```rust,no_run +//! use thread_priority::*; +//! +//! assert!(std::thread::current().get_priority().is_ok()); +//! println!("This thread's native id is: {:?}", std::thread::current().get_native_id()); +//! ``` +//! #![warn(missing_docs)] #![deny(warnings)] @@ -18,6 +91,7 @@ pub mod unix; #[cfg(unix)] pub use unix::*; + #[cfg(windows)] pub mod windows; #[cfg(windows)] @@ -31,19 +105,43 @@ pub enum Error { /// Target OS' error type. In most systems it is an integer which /// later should be used with target OS' API for understanding the value. OS(i32), - /// FFI failure + /// FFI failure. Ffi(&'static str), } +/// Platform-independent thread priority value. +/// Should be in `[0; 100)` range. The higher the number is - the higher +/// the priority. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ThreadPriorityValue(u8); +impl std::convert::TryFrom for ThreadPriorityValue { + type Error = &'static str; + + fn try_from(value: u8) -> Result { + if (0..=99).contains(&value) { + Ok(Self(value)) + } else { + Err("The value is not in the range of [0;99]") + } + } +} + +/// Platform-specific thread priority value. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ThreadPriorityOsValue(u32); + /// Thread priority enumeration. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum ThreadPriority { /// Holds a value representing the minimum possible priority. Min, - /// Holds a specific priority value. Should be in [0; 100] range, - /// a percentage value. The `u32` value is reserved for different - /// OS'es support. - Specific(u32), + /// Holds a platform-independent priority value. + /// Usually used when setting a value, for sometimes it is not possible to map + /// the operating system's priority to this value. + Crossplatform(ThreadPriorityValue), + /// Holds an operating system specific value. If it is not possible to obtain the + /// [`ThreadPriority::Crossplatform`] variant of the value, this is returned instead. + Os(ThreadPriorityOsValue), /// Holds scheduling parameters for Deadline scheduling. These are, in order, /// the nanoseconds for runtime, deadline, and period. Please note that the /// kernel enforces runtime <= deadline <= period. @@ -90,3 +188,330 @@ impl Thread { }) } } + +/// A copy of the [`std::thread::Builder`] builder allowing to set priority settings. +/// +/// ```rust +/// use thread_priority::*; +/// +/// let thread = ThreadBuilder::default() +/// .name("MyThread") +/// .priority(ThreadPriority::Max) +/// .spawn(|result| { +/// // This is printed out from within the spawned thread. +/// println!("Set priority result: {:?}", result); +/// assert!(result.is_ok()); +/// }).unwrap(); +/// thread.join(); +/// +/// // Another example where we don't care about the priority having been set. +/// let thread = ThreadBuilder::default() +/// .name("MyThread") +/// .priority(ThreadPriority::Max) +/// .spawn_careless(|| { +/// // This is printed out from within the spawned thread. +/// println!("We don't care about the priority result."); +/// }).unwrap(); +/// thread.join(); +/// ``` +#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct ThreadBuilder { + name: Option, + stack_size: Option, + priority: Option, + + #[cfg(unix)] + policy: Option, + + #[cfg(windows)] + winapi_priority: Option, + #[cfg(windows)] + boost_enabled: bool, + #[cfg(windows)] + ideal_processor: Option, +} + +impl ThreadBuilder { + /// Names the thread-to-be. Currently the name is used for identification + /// only in panic messages. + /// + /// The name must not contain null bytes (`\0`). + /// + /// For more information about named threads, see + /// [`std::thread::Builder::name()`]. + pub fn name>(mut self, value: VALUE) -> Self { + self.name = Some(value.into()); + self + } + + /// Sets the size of the stack (in bytes) for the new thread. + /// + /// The actual stack size may be greater than this value if + /// the platform specifies a minimal stack size. + /// + /// For more information about the stack size for threads, see + /// [`std::thread::Builder::stack_size()`]. + pub fn stack_size>(mut self, value: VALUE) -> Self { + self.stack_size = Some(value.into()); + self + } + + /// The thread's custom priority. + /// + /// For more information about the stack size for threads, see + /// [`ThreadPriority`]. + pub fn priority>(mut self, value: VALUE) -> Self { + self.priority = Some(value.into()); + self + } + + /// The thread's unix scheduling policy. + /// + /// For more information, see + /// [`crate::unix::ThreadSchedulePolicy`] and [`crate::unix::set_thread_priority_and_policy`]. + #[cfg(unix)] + pub fn policy>(mut self, value: VALUE) -> Self { + self.policy = Some(value.into()); + self + } + + /// The WinAPI priority representation. + /// + /// For more information, see + /// [`crate::windows::WinAPIThreadPriority`]. + #[cfg(windows)] + pub fn winapi_priority>( + mut self, + value: VALUE, + ) -> Self { + self.winapi_priority = Some(value.into()); + self + } + + /// Disables or enables the ability of the system to temporarily boost the priority of a thread. + /// + /// For more information, see + /// [`crate::windows::set_thread_priority_boost`]. + #[cfg(windows)] + pub fn boost_enabled(mut self, value: bool) -> Self { + self.boost_enabled = value; + self + } + + /// Sets a preferred processor for a thread. The system schedules threads on their preferred + /// processors whenever possible. + /// + /// For more information, see + /// [`crate::windows::set_thread_ideal_processor`]. + #[cfg(windows)] + pub fn ideal_processor>(mut self, value: VALUE) -> Self { + self.ideal_processor = Some(value.into()); + self + } + + /// Spawns a new thread by taking ownership of the `Builder`, and returns an + /// [`std::io::Result`] to its [`std::thread::JoinHandle`]. + /// + /// See [`std::thread::Builder::spawn`] + #[cfg(unix)] + pub fn spawn(mut self, f: F) -> std::io::Result> + where + F: FnOnce(Result<(), Error>) -> T, + F: Send + 'static, + T: Send + 'static, + { + let priority = self.priority; + let policy = self.policy; + + self.build_std().spawn(move || match (priority, policy) { + (Some(priority), Some(policy)) => f(set_thread_priority_and_policy( + thread_native_id(), + priority, + policy, + )), + (Some(priority), None) => f(priority.set_for_current()), + (None, Some(_policy)) => { + unimplemented!("Setting the policy separately isn't currently supported."); + } + _ => f(Ok(())), + }) + } + + /// Spawns a new thread by taking ownership of the `Builder`, and returns an + /// [`std::io::Result`] to its [`std::thread::JoinHandle`]. + /// + /// See [`std::thread::Builder::spawn`] + #[cfg(windows)] + pub fn spawn(mut self, f: F) -> std::io::Result> + where + F: FnOnce(Result<(), Error>) -> T, + F: Send + 'static, + T: Send + 'static, + { + let thread_priority = self.priority; + let winapi_priority = self.winapi_priority; + let boost_enabled = self.boost_enabled; + let ideal_processor = self.ideal_processor; + + self.build_std().spawn(move || { + let mut result = match (thread_priority, winapi_priority) { + (Some(priority), None) => set_thread_priority(thread_native_id(), priority), + (_, Some(priority)) => set_winapi_thread_priority(thread_native_id(), priority), + _ => Ok(()), + }; + if result.is_ok() && boost_enabled { + result = set_current_thread_priority_boost(boost_enabled); + } + if result.is_ok() { + if let Some(ideal_processor) = ideal_processor { + result = set_current_thread_ideal_processor(ideal_processor).map(|_| ()); + } + } + f(result) + }) + } + + fn build_std(&mut self) -> std::thread::Builder { + let mut builder = std::thread::Builder::new(); + + if let Some(name) = &self.name { + builder = builder.name(name.to_owned()); + } + + if let Some(stack_size) = self.stack_size { + builder = builder.stack_size(stack_size); + } + + builder + } + + /// Spawns a new thread by taking ownership of the `Builder`, and returns an + /// [`std::io::Result`] to its [`std::thread::JoinHandle`]. + /// + /// See [`std::thread::Builder::spawn`] + pub fn spawn_careless(self, f: F) -> std::io::Result> + where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, + { + self.spawn(|priority_set_result| { + if let Err(e) = priority_set_result { + log::warn!( + "Couldn't set the priority for the thread with Rust Thread ID {:?} named {:?}: {:?}", + std::thread::current().id(), + std::thread::current().name(), + e, + ); + } + + f() + }) + } +} + +/// Adds thread building functions using the priority. +pub trait ThreadBuilderExt { + /// Spawn a thread with set priority. The passed functor `f` is executed in the spawned thread and + /// receives as the only argument the result of setting the thread priority. + /// See [`std::thread::Builder::spawn`] and [`ThreadPriority::set_for_current`] for more info. + /// + /// # Example + /// + /// ```rust + /// use thread_priority::*; + /// use thread_priority::ThreadBuilderExt; + /// + /// let thread = std::thread::Builder::new() + /// .name("MyNewThread".to_owned()) + /// .spawn_with_priority(ThreadPriority::Max, |result| { + /// // This is printed out from within the spawned thread. + /// println!("Set priority result: {:?}", result); + /// assert!(result.is_ok()); + /// }).unwrap(); + /// thread.join(); + /// ``` + fn spawn_with_priority( + self, + priority: ThreadPriority, + f: F, + ) -> std::io::Result> + where + F: FnOnce(Result<(), Error>) -> T, + F: Send + 'static, + T: Send + 'static; +} + +impl ThreadBuilderExt for std::thread::Builder { + fn spawn_with_priority( + self, + priority: ThreadPriority, + f: F, + ) -> std::io::Result> + where + F: FnOnce(Result<(), Error>) -> T, + F: Send + 'static, + T: Send + 'static, + { + self.spawn(move || f(priority.set_for_current())) + } +} + +/// Spawns a thread with the specified priority. +/// +/// See [`ThreadBuilderExt::spawn_with_priority`]. +/// +/// ```rust +/// use thread_priority::*; +/// +/// let thread = spawn(ThreadPriority::Max, |result| { +/// // This is printed out from within the spawned thread. +/// println!("Set priority result: {:?}", result); +/// assert!(result.is_ok()); +/// }); +/// thread.join(); +/// ``` +pub fn spawn(priority: ThreadPriority, f: F) -> std::thread::JoinHandle +where + F: FnOnce(Result<(), Error>) -> T, + F: Send + 'static, + T: Send + 'static, +{ + std::thread::spawn(move || f(priority.set_for_current())) +} + +/// Spawns a thread with the specified priority. +/// This is different from [`spawn`] in a way that the passed function doesn't +/// need to accept the [`ThreadPriority::set_for_current`] result. +/// In case of an error, the error is logged using the logging facilities. +/// +/// See [`spawn`]. +/// +/// ```rust +/// use thread_priority::*; +/// +/// let thread = spawn_careless(ThreadPriority::Max, || { +/// // This is printed out from within the spawned thread. +/// println!("We don't care about the priority result."); +/// }); +/// thread.join(); +/// ``` +pub fn spawn_careless(priority: ThreadPriority, f: F) -> std::thread::JoinHandle +where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + std::thread::spawn(move || { + if let Err(e) = priority.set_for_current() { + log::warn!( + "Couldn't set the priority for the thread with Rust Thread ID {:?} named {:?}: {:?}", + std::thread::current().id(), + std::thread::current().name(), + e, + ); + } + + f() + }) +} diff --git a/src/unix.rs b/src/unix.rs index e4760f7..cbbb878 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -4,7 +4,9 @@ //! the unix threads, and this module provides //! better control over those. -use crate::{Error, ThreadPriority}; +use std::convert::TryFrom; + +use crate::{Error, ThreadPriority, ThreadPriorityValue}; /// An alias type for a thread id. pub type ThreadId = libc::pthread_t; @@ -78,7 +80,7 @@ impl ScheduleParams { /// 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)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum RealtimeThreadSchedulePolicy { /// A first-in, first-out policy Fifo, @@ -102,7 +104,7 @@ impl RealtimeThreadSchedulePolicy { } /// Normal (usual) schedule policies -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum NormalThreadSchedulePolicy { /// For running very low priority background jobs Idle, @@ -124,7 +126,7 @@ impl NormalThreadSchedulePolicy { } /// Thread schedule policy definition -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum ThreadSchedulePolicy { /// Normal thread schedule policies Normal(NormalThreadSchedulePolicy), @@ -179,7 +181,22 @@ impl ThreadPriority { ThreadSchedulePolicy::Realtime(_) => Ok(1), _ => Ok(0), }, - ThreadPriority::Specific(p) => match policy { + ThreadPriority::Crossplatform(ThreadPriorityValue(p)) => match policy { + // SCHED_DEADLINE doesn't really have a notion of priority, this is an error + #[cfg(target_os = "linux")] + ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err( + Error::Priority("Deadline scheduling must use deadline priority."), + ), + ThreadSchedulePolicy::Realtime(_) if (p == 0 || p > 99) => { + Err(Error::Priority("The value is out of range [0; 99]")) + } + ThreadSchedulePolicy::Normal(_) if p != 0 => Err(Error::Priority( + "The value can be only 0 for normal scheduling policy", + )), + _ => Ok(p 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(target_os = "linux")] ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err( @@ -214,7 +231,7 @@ impl ThreadPriority { /// In order to interpret it correctly, you should also take scheduling policy /// into account. pub fn from_posix(params: ScheduleParams) -> ThreadPriority { - ThreadPriority::Specific(params.sched_priority as u32) + ThreadPriority::Crossplatform(ThreadPriorityValue(params.sched_priority as u8)) } } @@ -375,6 +392,87 @@ pub fn thread_priority() -> Result { )) } +/// 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 [`thread_priority`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().get_priority().is_ok()); + /// ``` + fn get_priority(&self) -> Result { + 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_schedule_policy`]. + fn set_schedule_policy( + &self, + policy: ThreadSchedulePolicy, + priority: ThreadPriority, + ) -> Result<(), Error> { + let params = ScheduleParams { + sched_priority: match policy { + ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => 0, + _ => priority.to_posix(policy)?, + }, + }; + set_thread_schedule_policy(thread_native_id(), policy, params, priority) + } + + /// Returns native unix thread id. + /// For more info read [`thread_native_id`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().get_native_id() > 0); + fn get_native_id(&self) -> ThreadId { + thread_native_id() + } +} + +/// Auto-implementation of this trait for the [`std::thread::Thread`]. +impl ThreadExt for std::thread::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. @@ -390,6 +488,18 @@ 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::*; @@ -419,7 +529,7 @@ mod tests { .is_ok()); assert!(set_thread_priority_and_policy( thread_id, - ThreadPriority::Specific(0), + ThreadPriority::Crossplatform(ThreadPriorityValue(0)), ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Normal) ) .is_ok()); diff --git a/src/windows.rs b/src/windows.rs index 263c3fd..582e6a9 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -24,9 +24,9 @@ pub type IdealProcessor = DWORD; pub type ThreadId = HANDLE; /// The WinAPI priority representation. Check out MSDN for more info: -/// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority +/// #[repr(u32)] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum WinAPIThreadPriority { /// Begin background processing mode. The system lowers the resource /// scheduling priorities of the thread so that it can perform background @@ -74,14 +74,31 @@ impl std::convert::TryFrom for WinAPIThreadPriority { fn try_from(priority: ThreadPriority) -> Result { Ok(match priority { ThreadPriority::Min => WinAPIThreadPriority::Lowest, - ThreadPriority::Specific(p) => match p { + ThreadPriority::Crossplatform(crate::ThreadPriorityValue(p)) => match p { 0 => WinAPIThreadPriority::Idle, 1..=19 => WinAPIThreadPriority::Lowest, 21..=39 => WinAPIThreadPriority::BelowNormal, 41..=59 => WinAPIThreadPriority::Normal, 61..=79 => WinAPIThreadPriority::AboveNormal, - 81..=99 => WinAPIThreadPriority::Highest, - _ => return Err(Error::Priority("The value is out of range [0; 99]")), + 81..=98 => WinAPIThreadPriority::Highest, + 99 => WinAPIThreadPriority::TimeCritical, + _ => return Err(Error::Priority("The value is out of range [0; 99].")), + }, + ThreadPriority::Os(crate::ThreadPriorityOsValue(p)) => match p { + winbase::THREAD_MODE_BACKGROUND_BEGIN => WinAPIThreadPriority::BackgroundModeBegin, + winbase::THREAD_MODE_BACKGROUND_END => WinAPIThreadPriority::BackgroundModeEnd, + winbase::THREAD_PRIORITY_ABOVE_NORMAL => WinAPIThreadPriority::AboveNormal, + winbase::THREAD_PRIORITY_BELOW_NORMAL => WinAPIThreadPriority::BelowNormal, + winbase::THREAD_PRIORITY_HIGHEST => WinAPIThreadPriority::Highest, + winbase::THREAD_PRIORITY_IDLE => WinAPIThreadPriority::Idle, + winbase::THREAD_PRIORITY_LOWEST => WinAPIThreadPriority::Lowest, + winbase::THREAD_PRIORITY_NORMAL => WinAPIThreadPriority::Normal, + winbase::THREAD_PRIORITY_TIME_CRITICAL => WinAPIThreadPriority::TimeCritical, + _ => { + return Err(Error::Priority( + "The value is out of range of allowed values.", + )) + } }, ThreadPriority::Max => WinAPIThreadPriority::Highest, }) @@ -107,7 +124,13 @@ impl std::convert::TryFrom for WinAPIThreadPriority { } } -/// Sets thread's priority and schedule policy +impl From for crate::ThreadPriorityOsValue { + fn from(p: WinAPIThreadPriority) -> Self { + crate::ThreadPriorityOsValue(p as u32) + } +} + +/// Sets thread's priority and schedule policy. /// /// * May require privileges /// @@ -127,8 +150,32 @@ impl std::convert::TryFrom for WinAPIThreadPriority { pub fn set_thread_priority(native: ThreadId, priority: ThreadPriority) -> Result<(), Error> { use std::convert::TryFrom; + set_winapi_thread_priority(native, WinAPIThreadPriority::try_from(priority)?) +} + +/// Sets thread's priority and schedule policy using WinAPI priority values. +/// +/// * May require privileges +/// +/// # Usage +/// +/// Setting thread priority to minimum: +/// +/// ```rust +/// use thread_priority::*; +/// +/// let thread_id = thread_native_id(); +/// assert!(set_winapi_thread_priority(thread_id, WinAPIThreadPriority::Normal).is_ok()); +/// ``` +/// +/// If there's an error, a result of +/// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. +pub fn set_winapi_thread_priority( + native: ThreadId, + priority: WinAPIThreadPriority, +) -> Result<(), Error> { unsafe { - if SetThreadPriority(native, WinAPIThreadPriority::try_from(priority)? as c_int) != 0 { + if SetThreadPriority(native, priority as c_int) != 0 { Ok(()) } else { Err(Error::OS(GetLastError() as i32)) @@ -148,6 +195,7 @@ pub fn set_thread_priority(native: ThreadId, priority: ThreadPriority) -> Result /// use thread_priority::*; /// /// assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); +/// assert!(set_current_thread_priority(ThreadPriority::Os(WinAPIThreadPriority::Lowest.into())).is_ok()); /// ``` /// /// If there's an error, a result of @@ -177,9 +225,9 @@ pub fn thread_priority() -> Result { unsafe { let ret = GetThreadPriority(thread_native_id()); if ret as u32 != winbase::THREAD_PRIORITY_ERROR_RETURN { - Ok(ThreadPriority::Specific( + Ok(ThreadPriority::Os(crate::ThreadPriorityOsValue( WinAPIThreadPriority::try_from(ret as DWORD)? as u32, - )) + ))) } else { Err(Error::OS(GetLastError() as i32)) } @@ -279,3 +327,90 @@ pub fn set_current_thread_ideal_processor( ) -> Result { set_thread_ideal_processor(thread_native_id(), ideal_processor) } + +impl std::convert::TryFrom for crate::ThreadPriorityOsValue { + type Error = (); + + fn try_from(value: u32) -> Result { + Ok(crate::ThreadPriorityOsValue(match value { + winbase::THREAD_MODE_BACKGROUND_BEGIN + | winbase::THREAD_MODE_BACKGROUND_END + | winbase::THREAD_PRIORITY_ABOVE_NORMAL + | winbase::THREAD_PRIORITY_BELOW_NORMAL + | winbase::THREAD_PRIORITY_HIGHEST + | winbase::THREAD_PRIORITY_IDLE + | winbase::THREAD_PRIORITY_LOWEST + | winbase::THREAD_PRIORITY_NORMAL + | winbase::THREAD_PRIORITY_TIME_CRITICAL => value, + _ => return Err(()), + })) + } +} + +/// Windows-specific complemented part of the [`crate::ThreadExt`] trait. +pub trait ThreadExt { + /// Returns current thread's priority. + /// For more info see [`thread_priority`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().get_priority().is_ok()); + /// ``` + fn get_priority(&self) -> Result { + thread_priority() + } + + /// Sets current thread's priority. + /// For more info see [`set_current_thread_priority`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().set_priority(ThreadPriority::Min).is_ok()); + /// ``` + fn set_priority(&self, priority: ThreadPriority) -> Result<(), Error> { + set_current_thread_priority(priority) + } + + /// Returns current thread's windows id. + /// For more info see [`thread_native_id`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(!std::thread::current().get_native_id().is_null()); + /// ``` + fn get_native_id(&self) -> ThreadId { + thread_native_id() + } + /// Sets current thread's ideal processor. + /// For more info see [`set_current_thread_ideal_processor`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().set_ideal_processor(0).is_ok()); + /// ``` + fn set_ideal_processor( + &self, + ideal_processor: IdealProcessor, + ) -> Result { + set_current_thread_ideal_processor(ideal_processor) + } + + /// Sets current thread's priority boost. + /// For more info see [`set_current_thread_priority_boost`]. + /// + /// ```rust + /// use thread_priority::*; + /// + /// assert!(std::thread::current().set_priority_boost(true).is_ok()); + /// ``` + fn set_priority_boost(&self, enabled: bool) -> Result<(), Error> { + set_current_thread_priority_boost(enabled) + } +} + +/// Auto-implementation of this trait for the [`std::thread::Thread`]. +impl ThreadExt for std::thread::Thread {}