thread-priority-1.1.0/.cargo_vcs_info.json0000644000000001360000000000100141610ustar { "git": { "sha1": "3d0610c2243d7ccb148d9406314408f429889792" }, "path_in_vcs": "" }thread-priority-1.1.0/.github/FUNDING.yml000064400000000000000000000000151046102023000161220ustar 00000000000000github: iddm thread-priority-1.1.0/.github/workflows/ci.yml000064400000000000000000000075371046102023000175000ustar 00000000000000name: CI on: [push, pull_request] env: minrust: 1.67.1 jobs: test: name: Test runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: name: - stable - beta - nightly - macOS - Windows include: - name: beta toolchain: beta - name: nightly toolchain: nightly - name: macOS os: macOS-latest - name: Windows os: windows-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain id: tc uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain || 'stable' }} profile: minimal override: true - name: Setup cache if: runner.os != 'macOS' uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - name: Build all features if: matrix.features == '' run: cargo build --all-features - name: Test all features (macos) if: matrix.features == '' && runner.os == 'macOS' run: sudo -E /Users/runner/.cargo/bin/cargo test --all-features - name: Test all features (Windows) if: matrix.features == '' && runner.os == 'Windows' run: cargo test --all-features - name: Test all features (non-linux and non-macOS) if: matrix.features == '' && runner.os != 'Linux' && runner.os != 'macOS' && runner.os != 'Windows' run: sudo -E /home/runner/.cargo/bin/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 clippy: name: Run clippy runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain id: tc uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-clippy-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - run: cargo clippy MSRV: runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain (${{ env.minrust }}) id: tc uses: actions-rs/toolchain@v1 with: toolchain: ${{ env.minrust }} profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-msrv-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - run: cargo check --all-features doc: name: Build docs runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain id: tc uses: actions-rs/toolchain@v1 with: toolchain: nightly profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-docs-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - name: Build docs run: | cargo doc thread-priority-1.1.0/.github/workflows/lint.yml000064400000000000000000000025351046102023000200440ustar 00000000000000# Copied from Twilight's Lint workflow. # # https://github.com/twilight-rs/twilight/blob/trunk/.github/workflows/lint.yml name: Lint on: [push, pull_request] jobs: clippy: name: Clippy runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain id: toolchain uses: actions-rs/toolchain@v1 with: toolchain: nightly components: clippy profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-clippy-rustc-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Run clippy uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --tests rustfmt: name: Format runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: toolchain: nightly components: rustfmt profile: minimal override: true - name: Run cargo fmt run: cargo fmt --all -- --check thread-priority-1.1.0/.github/workflows/nightly.yml000064400000000000000000000075141046102023000205560ustar 00000000000000name: Nightly Build on: schedule: - cron: '0 2 * * *' env: minrust: 1.67.1 jobs: test: name: Test runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: name: - stable - beta - nightly - macOS - Windows include: - name: beta toolchain: beta - name: nightly toolchain: nightly - name: macOS os: macOS-latest - name: Windows os: windows-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain id: tc uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain || 'stable' }} profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - name: Build all features if: matrix.features == '' run: cargo build --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 runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: toolchain: nightly components: rustfmt profile: minimal override: true - name: Run cargo fmt run: cargo fmt --all -- --check clippy: name: Clippy runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain id: tc uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-clippy-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - run: cargo clippy MSRV: runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain (${{ env.minrust }}) id: tc uses: actions-rs/toolchain@v1 with: toolchain: ${{ env.minrust }} profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-msrv-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - run: cargo check --all-features doc: name: Build docs runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain id: tc uses: actions-rs/toolchain@v1 with: toolchain: nightly profile: minimal override: true - name: Setup cache uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-docs-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} - name: Build docs run: | cargo doc thread-priority-1.1.0/.gitignore000064400000000000000000000001261046102023000147400ustar 00000000000000target/ **/*.rs.bk Cargo.lock **/*.swo **/*.swp .idea/ Vagrantfile .vagrant/ .vscode/ thread-priority-1.1.0/Cargo.toml0000644000000031620000000000100121610ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.67.1" name = "thread-priority" version = "1.1.0" authors = ["Victor Polevoy "] description = "Library for managing threads priority and schedule policies" documentation = "https://docs.rs/thread-priority/" readme = "README.md" keywords = [ "thread", "schedule", "priority", "pthread", ] categories = [ "concurrency", "asynchronous", "os", ] license = "MIT" repository = "https://github.com/iddm/thread-priority" [dependencies.bitflags] version = "2" [dependencies.cfg-if] version = "1" [dependencies.log] version = "0.4" [dependencies.rustversion] version = "1" [dev-dependencies.rstest] version = "0.19" [target."cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"macos\", target_os = \"ios\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies.libc] version = ">=0.2.123" [target."cfg(windows)".dependencies.libc] version = ">=0.2.123" [target."cfg(windows)".dependencies.winapi] version = "0.3" features = [ "errhandlingapi", "processthreadsapi", "winnt", "minwindef", "winbase", ] thread-priority-1.1.0/Cargo.toml.orig000064400000000000000000000017171046102023000156460ustar 00000000000000[package] name = "thread-priority" version = "1.1.0" authors = ["Victor Polevoy "] description = "Library for managing threads priority and schedule policies" repository = "https://github.com/iddm/thread-priority" documentation = "https://docs.rs/thread-priority/" readme = "README.md" license = "MIT" keywords = ["thread", "schedule", "priority", "pthread"] categories = ["concurrency", "asynchronous", "os"] edition = "2021" rust-version = "1.67.1" [dev-dependencies] rstest = "0.19" [dependencies] log = "0.4" cfg-if = "1" rustversion = "1" bitflags = "2" [target.'cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] libc = ">=0.2.123" [target.'cfg(windows)'.dependencies] libc = ">=0.2.123" winapi = { version = "0.3", features = ["errhandlingapi", "processthreadsapi", "winnt", "minwindef", "winbase"] } thread-priority-1.1.0/LICENSE000064400000000000000000000020571046102023000137620ustar 00000000000000MIT License Copyright (c) 2017 Victor Polevoy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. thread-priority-1.1.0/README.md000064400000000000000000000067141046102023000142400ustar 00000000000000# thread-priority [![CI](https://github.com/iddm/thread-priority/actions/workflows/ci.yml/badge.svg)](https://github.com/iddm/thread-priority/actions/workflows/ci.yml) [![Crates](https://img.shields.io/crates/v/thread-priority.svg)](https://crates.io/crates/thread-priority) [![Docs](https://docs.rs/thread-priority/badge.svg)](https://docs.rs/thread-priority) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) A simple library to control thread schedule policies and thread priority. If your operating system isn't yet supported, please, create an issue. ## Minimal Rust Compiler Version Is `1.67`. If you need any help making it possible to compile with `1.56` please reach out, it should be possible by just downgrading the `rstest` version to `0.17` or lower (the code is compatible). ## Supported platforms - Linux - Android - DragonFly - FreeBSD - OpenBSD - NetBSD - macOS - iOS - Windows ## Examples ### Minimal cross-platform examples Setting current thread's priority to minimum: ```rust,no_run use thread_priority::*; fn main() { 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; 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/iddm/thread-priority/blob/master/LICENSE). thread-priority-1.1.0/src/lib.rs000064400000000000000000000735471046102023000146740ustar 00000000000000//! Thread priority. A library for changing thread's priority. //! //! # Usage //! //! Setting thread priority to minimum: //! //! ```rust //! use thread_priority::*; //! //! assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); //! // 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(); //! //! // This also support scoped thread. //! let x = 0; //! std::thread::scope(|s|{ //! std::thread::Builder::new() //! .name("MyNewThread".to_owned()) //! .spawn_scoped_with_priority(s, ThreadPriority::Max, |result| { //! // This is printed out from within the spawned thread. //! println!("Set priority result: {:?}", result); //! assert!(result.is_ok()); //! dbg!(&x); //! }).unwrap(); //! }); //! ``` //! //! ### Building a thread using the [`ThreadScopeExt`] trait //! //! ```rust,no_run //! use thread_priority::*; //! let x = 0; //! std::thread::scope(|s|{ //! s.spawn_with_priority(ThreadPriority::Max, |result| { //! // This is printed out from within the spawned thread. //! println!("Set priority result: {:?}", result); //! assert!(result.is_ok()); //! dbg!(&x); //! }); //! }); //! ``` //! //! ### 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(); //! //! // Scoped thread is also supported if the compiler version is at least 1.63. //! let mut x = 0; //! std::thread::scope(|s|{ //! let thread = ThreadBuilder::default() //! .name("MyThread") //! .priority(ThreadPriority::Max) //! .spawn_scoped(s, |result| { //! // This is printed out from within the spawned thread. //! println!("Set priority result: {:?}", result); //! assert!(result.is_ok()); //! x += 1; //! }).unwrap(); //! thread.join(); //! }); //! assert_eq!(x, 1); //! //! // Scoped thread also has a "careless" mode. //! std::thread::scope(|s|{ //! let thread = ThreadBuilder::default() //! .name("MyThread") //! .priority(ThreadPriority::Max) //! .spawn_scoped_careless(s, || { //! // This is printed out from within the spawned thread. //! println!("We don't care about the priority result."); //! x += 1; //! }).unwrap(); //! thread.join(); //! }); //! assert_eq!(x, 2); //! ``` //! //! ### 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)] #[cfg(any( target_os = "linux", target_os = "macos", target_os = "ios", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "android", target_arch = "wasm32", ))] pub mod unix; #[cfg(any(target_os = "linux", target_os = "android"))] use std::time::Duration; #[cfg(any( target_os = "linux", target_os = "macos", target_os = "ios", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "android", target_arch = "wasm32", ))] pub use unix::*; #[cfg(windows)] pub mod windows; #[cfg(windows)] pub use windows::*; /// A error type #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Error { /// A value which describes why it is impossible to use such a priority. Priority(&'static str), /// Indicates that the priority isn't in range and it should be within the provided range. /// This may happen on different operating systems following a single standard of API but /// allowing different priority values for different scheduling policies. PriorityNotInRange(std::ops::RangeInclusive), /// Target OS' error type. In most systems it is an integer which /// later should be used with target OS' API for understanding the value. /// On Linux there is an integer containing an error code from errno. /// For Windows it contains a number used in Windows for the same purpose. OS(i32), /// FFI failure. Ffi(&'static str), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::Priority(s) => write!(f, "unable to set priority: {}", s), Error::PriorityNotInRange(range) => { write!(f, "priority must be within the range: {:?}", range) } Error::OS(i) => write!(f, "the operating system returned error code {}", i), Error::Ffi(s) => write!(f, "FFI error: {}", s), } } } impl std::error::Error for Error {} /// Platform-independent thread priority value. /// Should be in `[0; 100)` range. The higher the number is - the higher /// the priority. /// /// The only way to create such a value is a safe conversion from an 8-byte /// unsigned integer ([`u8`]): /// /// ```rust /// use thread_priority::*; /// use std::convert::{TryFrom, TryInto}; /// /// // Create the lowest possible priority value. /// assert!(ThreadPriorityValue::try_from(0u8).is_ok()); /// // Create it implicitly via `TryInto`: /// let _priority = ThreadPriority::Crossplatform(0u8.try_into().unwrap()); /// ``` /// /// In case you need to get the raw value out of it, use the `Into` trait: /// /// ```rust /// use thread_priority::*; /// use std::convert::TryFrom; /// /// // Create the lowest possible priority value. /// let priority = ThreadPriorityValue::try_from(0u8).unwrap(); /// // Create it implicitly via `TryInto`: /// let raw_value: u8 = priority.into(); /// assert_eq!(raw_value, 0); /// ``` #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct ThreadPriorityValue(u8); impl ThreadPriorityValue { /// The maximum value for a thread priority. pub const MAX: u8 = 99; /// The minimum value for a thread priority. pub const MIN: u8 = 0; } impl std::convert::TryFrom for ThreadPriorityValue { type Error = &'static str; fn try_from(value: u8) -> Result { if (Self::MIN..=Self::MAX).contains(&value) { Ok(Self(value)) } else { Err("The value is not in the range of [0;99]") } } } // The From is unsafe, so there is a TryFrom instead. // For this reason we silent the warning from clippy. #[allow(clippy::from_over_into)] impl std::convert::Into for ThreadPriorityValue { fn into(self) -> u8 { self.0 } } /// 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, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum ThreadPriority { /// Holds a value representing the minimum possible priority. #[cfg_attr( target_os = "windows", doc = "\ The [`ThreadPriority::Min`] value is mapped to [`WinAPIThreadPriority::Lowest`] and not [`WinAPIThreadPriority::Idle`] to avoid unexpected drawbacks. Use the specific value to set it to [`WinAPIThreadPriority::Idle`] when it is really needed. " )] Min, /// 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. #[cfg_attr( target_os = "windows", doc = "\ The value is matched among possible values in Windows from [`WinAPIThreadPriority::Idle`] till [`WinAPIThreadPriority::TimeCritical`]. This is due to windows only having from 7 to 9 possible thread priorities and not `100` as it is allowed to have in the [`ThreadPriority::Crossplatform`] variant. " )] 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. /// /// arrival/wakeup absolute deadline /// | start time | /// | | | /// v v v /// -----x--------xooooooooooooooooo--------x--------x--- /// |<-- Runtime ------->| /// |<----------- Deadline ----------->| /// |<-------------- Period ------------------->| #[cfg(any(target_os = "linux", target_os = "android"))] Deadline { /// Set this to something larger than the average computation time /// or to the worst-case computation time for hard real-time tasks. runtime: Duration, /// Set this to the relative deadline. deadline: Duration, /// Set this to the period of the task. period: Duration, /// Deadline flags. flags: crate::unix::DeadlineFlags, }, /// Holds a value representing the maximum possible priority. /// Should be used with caution, it solely depends on the target /// os where the program is going to be running on, how it will /// behave. On some systems, the whole system may become frozen /// if not used properly. #[cfg_attr( target_os = "windows", doc = "\ The [`ThreadPriority::Max`] value is mapped to [`WinAPIThreadPriority::Highest`] and not [`WinAPIThreadPriority::TimeCritical`] to avoid unexpected drawbacks. Use the specific value to set it to [`WinAPIThreadPriority::TimeCritical`] when it is really needed. " )] Max, } impl ThreadPriority { /// Sets current thread's priority to this value. pub fn set_for_current(self) -> Result<(), Error> { set_current_thread_priority(self) } } /// Represents an OS thread. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct Thread { /// Thread's priority. pub priority: ThreadPriority, /// Thread's ID (or handle). pub id: ThreadId, } impl Thread { /// Get current thread. /// /// # Usage /// /// ```rust /// use thread_priority::*; /// /// assert!(Thread::current().is_ok()); /// ``` pub fn current() -> Result { Ok(Thread { priority: get_current_thread_priority()?, id: thread_native_id(), }) } } /// A wrapper producing a closure where the input priority set result is logged on error, but no other handling is performed fn careless_wrapper(f: F) -> impl FnOnce(Result<(), Error>) -> T where F: FnOnce() -> T + Send, T: Send, { |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() } } /// 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(); /// ``` /// /// If the compiler version is at least 1.63, the scoped thread support is also enabled. /// /// ```rust /// use thread_priority::*; /// /// // Scoped thread is also supported if the compiler version is at least 1.63. /// let mut x = 0; /// std::thread::scope(|s|{ /// let thread = ThreadBuilder::default() /// .name("MyThread") /// .priority(ThreadPriority::Max) /// .spawn_scoped(s, |result| { /// // This is printed out from within the spawned thread. /// println!("Set priority result: {:?}", result); /// assert!(result.is_ok()); /// x += 1; /// }).unwrap(); /// thread.join(); /// }); /// assert_eq!(x, 1); /// /// // Scoped thread also has a "careless" mode. /// std::thread::scope(|s|{ /// let thread = ThreadBuilder::default() /// .name("MyThread") /// .priority(ThreadPriority::Max) /// .spawn_scoped_careless(s, || { /// // This is printed out from within the spawned thread. /// println!("We don't care about the priority result."); /// x += 1; /// }).unwrap(); /// thread.join(); /// }); /// assert_eq!(x, 2); /// ``` #[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 } #[cfg(unix)] fn spawn_wrapper(self, f: F) -> impl FnOnce() -> T where F: FnOnce(Result<(), Error>) -> T, F: Send, T: Send, { move || match (self.priority, self.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(())), } } #[cfg(windows)] fn spawn_wrapper(self, f: F) -> impl FnOnce() -> T where F: FnOnce(Result<(), Error>) -> T, F: Send, T: Send, { move || { let mut result = match (self.priority, self.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() && self.boost_enabled { result = set_current_thread_priority_boost(self.boost_enabled); } if result.is_ok() { if let Some(ideal_processor) = self.ideal_processor { result = set_current_thread_ideal_processor(ideal_processor).map(|_| ()); } } f(result) } } /// 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(mut self, f: F) -> std::io::Result> where F: FnOnce(Result<(), Error>) -> T, F: Send + 'static, T: Send + 'static, { self.build_std().spawn(self.spawn_wrapper(f)) } /// Spawns a new scoped thread by taking ownership of the `Builder`, and returns an /// [`std::io::Result`] to its [`std::thread::ScopedJoinHandle`]. /// /// See [`std::thread::Builder::spawn_scoped`] #[rustversion::since(1.63)] pub fn spawn_scoped<'scope, 'env, F, T>( mut self, scope: &'scope std::thread::Scope<'scope, 'env>, f: F, ) -> std::io::Result> where F: FnOnce(Result<(), Error>) -> T, F: Send + 'scope, T: Send + 'scope, { self.build_std().spawn_scoped(scope, self.spawn_wrapper(f)) } 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(careless_wrapper(f)) } /// Spawns a new scoped thread by taking ownership of the `Builder`, and returns an /// [`std::io::Result`] to its [`std::thread::ScopedJoinHandle`]. /// /// See [`std::thread::Builder::spawn_scoped`] #[rustversion::since(1.63)] pub fn spawn_scoped_careless<'scope, 'env, F, T>( self, scope: &'scope std::thread::Scope<'scope, 'env>, f: F, ) -> std::io::Result> where F: FnOnce() -> T, F: Send + 'scope, T: Send + 'scope, { self.spawn_scoped(scope, careless_wrapper(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; /// Spawn a scoped 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_scoped`] and [`ThreadPriority::set_for_current`] for more info. /// /// # Example /// /// ```rust /// use thread_priority::*; /// use thread_priority::ThreadBuilderExt; /// /// let x = 0; /// /// std::thread::scope(|s|{ /// std::thread::Builder::new() /// .name("MyNewThread".to_owned()) /// .spawn_scoped_with_priority(s, ThreadPriority::Max, |result| { /// // This is printed out from within the spawned thread. /// println!("Set priority result: {:?}", result); /// assert!(result.is_ok()); /// dbg!(&x); /// }).unwrap(); /// }); /// ``` #[rustversion::since(1.63)] fn spawn_scoped_with_priority<'scope, 'env, F, T>( self, scope: &'scope std::thread::Scope<'scope, 'env>, priority: ThreadPriority, f: F, ) -> std::io::Result> where F: FnOnce(Result<(), Error>) -> T, F: Send + 'scope, T: Send + 'scope; } 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())) } #[rustversion::since(1.63)] fn spawn_scoped_with_priority<'scope, 'env, F, T>( self, scope: &'scope std::thread::Scope<'scope, 'env>, priority: ThreadPriority, f: F, ) -> std::io::Result> where F: FnOnce(Result<(), Error>) -> T, F: Send + 'scope, T: Send + 'scope, { self.spawn_scoped(scope, move || f(priority.set_for_current())) } } /// Adds scoped thread building functions using the priority. #[rustversion::since(1.63)] pub trait ThreadScopeExt<'scope> { /// Spawn a scoped 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::Scope::spawn`] and [`ThreadPriority::set_for_current`] for more info. /// /// # Example /// /// ```rust /// use thread_priority::*; /// /// let x = 0; /// /// std::thread::scope(|s|{ /// s.spawn_with_priority(ThreadPriority::Max, |result| { /// // This is printed out from within the spawned thread. /// println!("Set priority result: {:?}", result); /// assert!(result.is_ok()); /// dbg!(&x); /// }); /// }); /// ``` fn spawn_with_priority( &'scope self, priority: ThreadPriority, f: F, ) -> std::thread::ScopedJoinHandle<'scope, T> where F: FnOnce(Result<(), Error>) -> T, F: Send + 'scope, T: Send + 'scope; } #[rustversion::since(1.63)] impl<'scope, 'env> ThreadScopeExt<'scope> for std::thread::Scope<'scope, 'env> { fn spawn_with_priority( &'scope self, priority: ThreadPriority, f: F, ) -> std::thread::ScopedJoinHandle<'scope, T> where F: FnOnce(Result<(), Error>) -> T, F: Send + 'scope, T: Send + 'scope, { 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 scoped thread with the specified priority. /// /// See [`ThreadBuilderExt::spawn_with_priority`]. /// /// ```rust /// use thread_priority::*; /// /// let x = 0; /// /// std::thread::scope(|s| { /// spawn_scoped(s, ThreadPriority::Max, |result| { /// // This is printed out from within the spawned thread. /// println!("Set priority result: {:?}", result); /// assert!(result.is_ok()); /// dbg!(&x); /// }); /// }); /// ``` #[rustversion::since(1.63)] pub fn spawn_scoped<'scope, 'env, F, T>( scope: &'scope std::thread::Scope<'scope, 'env>, priority: ThreadPriority, f: F, ) -> std::io::Result> where F: FnOnce(Result<(), Error>) -> T, F: Send + 'scope, T: Send + 'scope, { Ok(scope.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 || careless_wrapper(f)(priority.set_for_current())) } /// Spawns a scoped thread with the specified priority. /// This is different from [`spawn_scoped`] 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_scoped`]. /// /// ```rust /// use thread_priority::*; /// /// let x = 0; /// /// std::thread::scope(|s| { /// spawn_scoped_careless(s, ThreadPriority::Max, || { /// // This is printed out from within the spawned thread. /// println!("We don't care about the priority result."); /// dbg!(&x); /// }); /// }); /// ``` #[rustversion::since(1.63)] pub fn spawn_scoped_careless<'scope, 'env, F, T>( scope: &'scope std::thread::Scope<'scope, 'env>, priority: ThreadPriority, f: F, ) -> std::io::Result> where F: FnOnce() -> T, F: Send + 'scope, T: Send + 'scope, { Ok(scope.spawn(move || careless_wrapper(f)(priority.set_for_current()))) } thread-priority-1.1.0/src/unix.rs000064400000000000000000001041501046102023000150720ustar 00000000000000//! 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(target_os = "android"))] use libc::SCHED_OTHER; #[cfg(any(target_os = "linux", target_os = "android"))] use libc::{SCHED_BATCH, SCHED_IDLE}; use libc::{SCHED_FIFO, SCHED_RR}; use crate::{Error, ThreadPriority, ThreadPriorityValue}; use std::mem::MaybeUninit; // 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 { 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 { 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"))] { // 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 { libc::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 { libc::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"), 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)?; // 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")) { // 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 { libc::pthread_setschedparam( native, policy.to_posix(), ¶ms as *const libc::sched_param, ) }; match ret { 0 => Ok(()), e => Err(Error::OS(e)), } } else { // Normal priority threads must be set with static priority 0. let params = ScheduleParams { sched_priority: 0 }.into_posix(); let ret = unsafe { libc::pthread_setschedparam( native, policy.to_posix(), ¶ms as *const libc::sched_param, ) }; if ret != 0 { return Err(Error::OS(ret)); } // Normal priority threads adjust relative priority through niceness. set_errno(0); let ret = unsafe { libc::setpriority(libc::PRIO_PROCESS, 0, fixed_priority) }; if ret != 0 { return Err(Error::OS(errno())); } Ok(()) } } } } /// 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 = libc::pthread_getschedparam( native, &mut policy as *mut libc::c_int, &mut params as *mut libc::sched_param, ); match ret { 0 => Ok(( ThreadSchedulePolicy::from_posix(policy)?, ScheduleParams::from_posix(params), )), e => Err(Error::OS(e)), } } } /// 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()); } } thread-priority-1.1.0/src/windows.rs000064400000000000000000000400371046102023000156040ustar 00000000000000//! This module defines the windows thread control. //! //! The crate's prelude doesn't have much control over //! the windows threads, and this module provides //! better control over those. use winapi::ctypes::c_int; use winapi::shared::minwindef::DWORD; use winapi::um::errhandlingapi::GetLastError; use winapi::um::processthreadsapi::{ GetCurrentThread, GetThreadPriority, SetThreadIdealProcessor, SetThreadPriority, SetThreadPriorityBoost, }; use winapi::um::winbase; use winapi::um::winnt::HANDLE; use crate::{Error, ThreadPriority}; /// An alias type for specifying the ideal processor. /// Used in the WinAPI for affinity control. pub type IdealProcessor = DWORD; /// An alias type for a thread id. pub type ThreadId = HANDLE; /// The WinAPI priority representation. Check out MSDN for more info: /// #[repr(u32)] #[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 /// work without significantly affecting activity in the foreground. /// /// This value can be specified only if hThread is a handle to the current /// thread. The function fails if the thread is already in background processing mode. /// /// # Warning /// Windows Server 2003: This value is not supported. BackgroundModeBegin = winbase::THREAD_MODE_BACKGROUND_BEGIN, /// End background processing mode. The system restores the resource /// scheduling priorities of the thread as they were before the thread /// entered background processing mode. /// /// This value can be specified only if hThread is a handle to the current thread. /// The function fails if the thread is not in background processing mode. /// /// # Warning /// Windows Server 2003: This value is not supported. BackgroundModeEnd = winbase::THREAD_MODE_BACKGROUND_END, /// Priority 1 point above the priority class. AboveNormal = winbase::THREAD_PRIORITY_ABOVE_NORMAL, /// Priority 1 point below the priority class. BelowNormal = winbase::THREAD_PRIORITY_BELOW_NORMAL, /// Priority 2 points above the priority class. Highest = winbase::THREAD_PRIORITY_HIGHEST, /// Base priority of 1 for IDLE_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, /// NORMAL_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS /// processes, and a base priority of 16 for REALTIME_PRIORITY_CLASS processes. Idle = winbase::THREAD_PRIORITY_IDLE, /// Priority 2 points below the priority class. Lowest = winbase::THREAD_PRIORITY_LOWEST, /// Normal priority for the priority class. Normal = winbase::THREAD_PRIORITY_NORMAL, /// Base priority of 15 for IDLE_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, /// NORMAL_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS /// processes, and a base priority of 31 for REALTIME_PRIORITY_CLASS processes. TimeCritical = winbase::THREAD_PRIORITY_TIME_CRITICAL, } impl std::convert::TryFrom for WinAPIThreadPriority { type Error = crate::Error; fn try_from(priority: ThreadPriority) -> Result { Ok(match priority { ThreadPriority::Min => WinAPIThreadPriority::Lowest, 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..=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, }) } } impl std::convert::TryFrom for WinAPIThreadPriority { type Error = crate::Error; fn try_from(priority: DWORD) -> Result { Ok(match priority { 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("Priority couldn't be parsed")), }) } } 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 /// /// # Usage /// /// Setting thread priority to minimum: /// /// ```rust /// use thread_priority::*; /// /// let thread_id = thread_native_id(); /// assert!(set_thread_priority(thread_id, ThreadPriority::Min).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_thread_priority(native: ThreadId, priority: ThreadPriority) -> Result<(), Error> { 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, priority as c_int) != 0 { Ok(()) } else { Err(Error::OS(GetLastError() as i32)) } } } /// Set current thread's priority. /// /// * May require privileges /// /// # Usage /// /// Setting thread priority to minimum: /// /// ```rust /// 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 /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. pub fn set_current_thread_priority(priority: ThreadPriority) -> Result<(), Error> { let thread_id = thread_native_id(); set_thread_priority(thread_id, priority) } /// Get the thread's priority value. /// /// Returns current thread's priority. /// /// # Usage /// /// ```rust /// use thread_priority::{thread_native_id, get_thread_priority}; /// /// assert!(get_thread_priority(thread_native_id()).is_ok()); /// ``` pub fn get_thread_priority(native: ThreadId) -> Result { unsafe { let ret = GetThreadPriority(native); if ret as u32 != winbase::THREAD_PRIORITY_ERROR_RETURN { Ok(ThreadPriority::Os(crate::ThreadPriorityOsValue( WinAPIThreadPriority::try_from(ret as DWORD)? as u32, ))) } else { Err(Error::OS(GetLastError() as i32)) } } } /// Get current thread's priority value. /// /// Returns current thread's priority. /// /// # Usage /// /// ```rust /// use thread_priority::get_current_thread_priority; /// /// assert!(get_current_thread_priority().is_ok()); /// ``` pub fn get_current_thread_priority() -> Result { unsafe { let ret = GetThreadPriority(thread_native_id()); if ret as u32 != winbase::THREAD_PRIORITY_ERROR_RETURN { Ok(ThreadPriority::Os(crate::ThreadPriorityOsValue( WinAPIThreadPriority::try_from(ret as DWORD)? as u32, ))) } else { Err(Error::OS(GetLastError() as i32)) } } } /// 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().is_null()); /// ``` pub fn thread_native_id() -> ThreadId { unsafe { GetCurrentThread() } } /// Disables or enables the ability of the system to temporarily boost the priority of a thread. /// /// If there's an error, a result of /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. /// /// # Usage /// /// ```rust /// use thread_priority::*; /// /// let thread_id = thread_native_id(); /// assert!(set_thread_priority_boost(thread_id, false).is_ok()) /// ``` pub fn set_thread_priority_boost(native: ThreadId, enabled: bool) -> Result<(), Error> { unsafe { if SetThreadPriorityBoost(native, enabled as i32) != 0 { Ok(()) } else { Err(Error::OS(GetLastError() as i32)) } } } /// Disables or enables the ability of the system to temporarily boost the priority of a current thread. /// /// If there's an error, a result of /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. /// /// This is a short-hand of the `set_thread_priority_boost` function for the current thread. pub fn set_current_thread_priority_boost(enabled: bool) -> Result<(), Error> { set_thread_priority_boost(thread_native_id(), enabled) } /// Sets a preferred processor for a thread. The system schedules threads on their preferred /// processors whenever possible. /// /// On a system with more than 64 processors, this function sets the preferred processor to a /// logical processor in the processor group to which the calling thread is assigned. Use the /// `SetThreadIdealProcessorEx` function to specify a processor group and preferred processor. /// /// If there's an error, a result of /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. /// On success, the function returns a previously assigned processor. /// /// # Note /// The processor number starts with zero. /// /// # Usage /// /// ```rust /// use thread_priority::*; /// /// let thread_id = thread_native_id(); /// assert!(set_thread_ideal_processor(thread_id, 0).is_ok()) /// ``` pub fn set_thread_ideal_processor( native: ThreadId, ideal_processor: IdealProcessor, ) -> Result { unsafe { let ret = SetThreadIdealProcessor(native, ideal_processor); if ret == IdealProcessor::max_value() - 1 { Err(Error::OS(GetLastError() as i32)) } else { Ok(ret) } } } /// Sets a preferred processor for a current thread. The system schedules threads on their preferred /// processors whenever possible. /// /// This is a short-hand of the `set_thread_ideal_processor` function for the current thread. pub fn set_current_thread_ideal_processor( ideal_processor: IdealProcessor, ) -> 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 { get_current_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().unwrap().is_null()); /// ``` fn get_native_id(&self) -> Result; /// 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 { 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.")) } } } thread-priority-1.1.0/tests/common.rs000064400000000000000000000021131046102023000157460ustar 00000000000000use rstest::rstest; #[rstest] fn should_be_possible_to_reset_the_same_priority() -> Result<(), Box> { let current = thread_priority::get_current_thread_priority()?; thread_priority::set_current_thread_priority(current)?; Ok(()) } #[rstest] fn should_be_possible_to_get_current_thread_native_id_via_threadext() { use thread_priority::ThreadExt; let current = std::thread::current(); #[cfg(unix)] assert_eq!( current.get_native_id(), Ok(thread_priority::unix::thread_native_id()) ); #[cfg(windows)] assert_eq!( current.get_native_id(), Ok(thread_priority::windows::thread_native_id()) ); } #[rstest] fn should_be_impossible_to_get_other_thread_native_id_via_threadext() { use thread_priority::ThreadExt; let current = std::thread::current(); let another_thread = std::thread::spawn(move || { #[cfg(unix)] assert!(current.get_native_id().is_err()); #[cfg(windows)] assert!(current.get_native_id().is_err()); }); another_thread.join().unwrap(); } thread-priority-1.1.0/tests/unix.rs000064400000000000000000000160021046102023000154430ustar 00000000000000#![cfg(not(windows))] use rstest::rstest; use std::convert::TryInto; use thread_priority::*; #[cfg(target_os = "linux")] #[test] fn get_and_set_priority_with_normal_and_crossplatform() { let nice = unsafe { libc::getpriority(0, 0) }; assert_eq!(nice, 0); crate::set_current_thread_priority(ThreadPriority::Crossplatform(30u8.try_into().unwrap())) .unwrap(); let nice = unsafe { libc::getpriority(0, 0) }; assert!(nice > 0); // Note that increasing priority requires extra permissions (e.g. sudo) crate::set_current_thread_priority(ThreadPriority::Crossplatform(70u8.try_into().unwrap())) .unwrap(); let nice = unsafe { libc::getpriority(0, 0) }; assert!(nice < 0); } #[cfg(target_os = "linux")] #[rstest] fn get_and_set_priority_with_normal_policies( #[values( ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other), ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle), ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Batch) )] policy: ThreadSchedulePolicy, #[values(ThreadPriority::Min, ThreadPriority::Max, ThreadPriority::Crossplatform(23u8.try_into().unwrap()))] priority: ThreadPriority, ) { let ret = set_thread_priority_and_policy(thread_native_id(), priority, policy); if policy == ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle) && priority == ThreadPriority::Crossplatform(23u8.try_into().unwrap()) { assert_eq!(ret, Err(Error::PriorityNotInRange(0..=0))); } else { assert!(ret.is_ok()); } } // In macOS it is allowed to specify number as a SCHED_OTHER policy priority. #[cfg(any( target_os = "macos", target_os = "openbsd", target_os = "freebsd", target_os = "netbsd" ))] #[rstest] fn get_and_set_priority_with_normal_policies( #[values(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other))] policy: ThreadSchedulePolicy, #[values(ThreadPriority::Min, ThreadPriority::Max, ThreadPriority::Crossplatform(23u8.try_into().unwrap()))] priority: ThreadPriority, ) { assert!(set_thread_priority_and_policy(thread_native_id(), priority, policy).is_ok()); } #[rstest] #[cfg(target_os = "linux")] #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle), 0..=0)] #[cfg(target_os = "linux")] #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Batch), -20..=19)] #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other), -20..=19)] #[case(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo), 0..=99)] #[case(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin), 0..=99)] fn check_min_and_max_priority_values( #[case] policy: ThreadSchedulePolicy, #[case] posix_range: std::ops::RangeInclusive, ) { let max_value = ThreadPriority::max_value_for_policy(policy).unwrap(); let min_value = ThreadPriority::min_value_for_policy(policy).unwrap(); assert!(posix_range.contains(&max_value)); assert!(posix_range.contains(&min_value)); } #[rstest] #[cfg(target_os = "linux")] #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle))] #[cfg(target_os = "linux")] #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Batch))] #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other))] fn set_priority_with_normal_policy_but_with_invalid_value(#[case] policy: ThreadSchedulePolicy) { let thread_id = thread_native_id(); #[cfg(target_os = "linux")] let expected = if policy == ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle) { // In Linux we should get an error whenever a non-zero value is passed as priority and a normal // scheduling policy is used. Err(Error::PriorityNotInRange(0..=0)) } else { Ok(()) }; assert_eq!( set_thread_priority_and_policy( thread_id, ThreadPriority::Crossplatform(23u8.try_into().unwrap()), policy, ), expected ); } #[cfg(any( target_os = "macos", target_os = "openbsd", target_os = "freebsd", target_os = "netbsd" ))] #[test] // In macOS the SCHED_OTHER policy allows having a non-zero priority value, // but the crate doesn't use this opportunity for normal threads and uses niceness instead. fn get_and_set_priority_with_normal_policy() { let thread_id = thread_native_id(); let normal_policy = ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other); assert_eq!( set_thread_priority_and_policy( thread_id, ThreadPriority::Crossplatform(23u8.try_into().unwrap()), normal_policy, ), Ok(()) ); assert_eq!(thread_schedule_policy(), Ok(normal_policy)); } #[rstest] #[case::fifo(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo))] #[case::roundrobin(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin))] fn get_and_set_priority_with_realtime_policy_requires_capabilities( #[case] realtime_policy: ThreadSchedulePolicy, ) { let thread_id = thread_native_id(); let max_value = ThreadPriority::max_value_for_policy(realtime_policy).unwrap(); let min_value = ThreadPriority::min_value_for_policy(realtime_policy).unwrap(); assert_eq!( set_thread_priority_and_policy(thread_id, ThreadPriority::Max, realtime_policy,), Ok(()) ); assert_eq!(thread_schedule_policy(), Ok(realtime_policy)); assert_eq!( thread_schedule_policy_param(thread_native_id()), Ok(( realtime_policy, ScheduleParams { sched_priority: max_value } )) ); assert_eq!( Thread::current(), Ok(Thread { priority: ThreadPriority::Crossplatform((max_value as u8).try_into().unwrap()), id: thread_native_id() }) ); assert_eq!( set_thread_priority_and_policy( thread_id, ThreadPriority::Crossplatform(23u8.try_into().unwrap()), realtime_policy, ), Ok(()) ); assert_eq!(thread_schedule_policy(), Ok(realtime_policy)); assert_eq!( thread_schedule_policy_param(thread_native_id()), Ok((realtime_policy, ScheduleParams { sched_priority: 23 })) ); assert_eq!( Thread::current(), Ok(Thread { priority: ThreadPriority::Crossplatform(23u8.try_into().unwrap()), id: thread_native_id() }) ); assert_eq!( set_thread_priority_and_policy(thread_id, ThreadPriority::Min, realtime_policy,), Ok(()) ); assert_eq!(thread_schedule_policy(), Ok(realtime_policy)); assert_eq!( thread_schedule_policy_param(thread_native_id()), Ok(( realtime_policy, ScheduleParams { sched_priority: min_value } )) ); assert_eq!( Thread::current(), Ok(Thread { priority: ThreadPriority::Crossplatform((min_value as u8).try_into().unwrap()), id: thread_native_id() }) ); } thread-priority-1.1.0/tests/windows.rs000064400000000000000000000015011046102023000161500ustar 00000000000000#![cfg(windows)] use rstest::rstest; use std::convert::TryInto; use thread_priority::*; #[rstest] #[case(ThreadPriority::Min, ThreadPriority::Os(WinAPIThreadPriority::Lowest.try_into().unwrap()))] #[case(ThreadPriority::Crossplatform(23u8.try_into().unwrap()), ThreadPriority::Os(WinAPIThreadPriority::BelowNormal.try_into().unwrap()))] #[case(ThreadPriority::Max, ThreadPriority::Os(WinAPIThreadPriority::Highest.try_into().unwrap()))] fn get_and_set_priority_requires_capabilities( #[case] input_priority: ThreadPriority, #[case] expected_priority: ThreadPriority, ) { let thread_id = thread_native_id(); let set_result = set_thread_priority(thread_id, input_priority); let get_result = get_thread_priority(thread_id); assert_eq!(set_result, Ok(())); assert_eq!(get_result, Ok(expected_priority),); }