├── .github ├── FUNDING.yml └── workflows │ ├── lint.yml │ ├── ci.yml │ └── nightly.yml ├── .gitignore ├── CHANGELOG.md ├── tests ├── windows.rs ├── common.rs └── unix.rs ├── LICENSE ├── Cargo.toml ├── README.md └── src ├── windows.rs ├── lib.rs └── unix.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: iddm 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | **/*.swo 5 | **/*.swp 6 | .idea/ 7 | Vagrantfile 8 | .vagrant/ 9 | .vscode/ 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.0.0 on 03-09-2025 2 | 3 | **Breaking changes**: 4 | 5 | - The `ThreadPriorityValue::MAX` and `ThreadPriorityValue::MIN` are now 6 | of type `ThreadPriorityValue` itself, instead of `u8`. Addresses the 7 | issue #38. 8 | 9 | # 2.1.1 on 03-09-2025 10 | 11 | - Corrected the git history. 12 | - Added the changelog file to help tracking the changes. 13 | -------------------------------------------------------------------------------- /tests/windows.rs: -------------------------------------------------------------------------------- 1 | #![cfg(windows)] 2 | use rstest::rstest; 3 | use std::convert::TryInto; 4 | use thread_priority::*; 5 | 6 | #[rstest] 7 | #[case(ThreadPriority::Min, ThreadPriority::Os(WinAPIThreadPriority::Lowest.try_into().unwrap()))] 8 | #[case(ThreadPriority::Crossplatform(23u8.try_into().unwrap()), ThreadPriority::Os(WinAPIThreadPriority::BelowNormal.try_into().unwrap()))] 9 | #[case(ThreadPriority::Crossplatform(80u8.try_into().unwrap()), ThreadPriority::Os(WinAPIThreadPriority::Highest.try_into().unwrap()))] 10 | #[case(ThreadPriority::Max, ThreadPriority::Os(WinAPIThreadPriority::Highest.try_into().unwrap()))] 11 | fn get_and_set_priority_requires_capabilities( 12 | #[case] input_priority: ThreadPriority, 13 | #[case] expected_priority: ThreadPriority, 14 | ) { 15 | let thread_id = thread_native_id(); 16 | 17 | let set_result = set_thread_priority(thread_id, input_priority); 18 | let get_result = get_thread_priority(thread_id); 19 | assert_eq!(set_result, Ok(())); 20 | assert_eq!(get_result, Ok(expected_priority),); 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Victor Polevoy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thread-priority" 3 | version = "3.0.0" 4 | authors = ["Victor Polevoy "] 5 | description = "Library for managing threads priority and schedule policies" 6 | repository = "https://github.com/iddm/thread-priority" 7 | documentation = "https://docs.rs/thread-priority/" 8 | readme = "README.md" 9 | license = "MIT" 10 | keywords = ["thread", "schedule", "priority", "pthread"] 11 | categories = ["concurrency", "asynchronous", "os"] 12 | edition = "2024" 13 | rust-version = "1.85" 14 | 15 | [dev-dependencies] 16 | rstest = "0.25" 17 | 18 | [dependencies] 19 | log = "0.4" 20 | cfg-if = "1" 21 | rustversion = "1" 22 | bitflags = "2" 23 | 24 | [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] 25 | libc = ">=0.2.123" 26 | 27 | [target.'cfg(target_os = "vxworks")'.dependencies] 28 | libc = ">=0.2.161" 29 | 30 | [target.'cfg(windows)'.dependencies] 31 | libc = ">=0.2.123" 32 | windows = { version = "0.61", features = [ 33 | "Win32", 34 | "Win32_System", 35 | "Win32_System_Threading", 36 | "Win32_System_WindowsProgramming", 37 | ] } 38 | -------------------------------------------------------------------------------- /tests/common.rs: -------------------------------------------------------------------------------- 1 | use rstest::rstest; 2 | 3 | #[rstest] 4 | fn should_be_possible_to_reset_the_same_priority() -> Result<(), Box> { 5 | let current = thread_priority::get_current_thread_priority()?; 6 | thread_priority::set_current_thread_priority(current)?; 7 | Ok(()) 8 | } 9 | 10 | #[rstest] 11 | fn should_be_possible_to_get_current_thread_native_id_via_threadext() { 12 | use thread_priority::ThreadExt; 13 | 14 | let current = std::thread::current(); 15 | #[cfg(unix)] 16 | assert_eq!( 17 | current.get_native_id(), 18 | Ok(thread_priority::unix::thread_native_id()) 19 | ); 20 | #[cfg(windows)] 21 | assert_eq!( 22 | current.get_native_id(), 23 | Ok(thread_priority::windows::thread_native_id()) 24 | ); 25 | } 26 | 27 | #[rstest] 28 | fn should_be_impossible_to_get_other_thread_native_id_via_threadext() { 29 | use thread_priority::ThreadExt; 30 | 31 | let current = std::thread::current(); 32 | let another_thread = std::thread::spawn(move || { 33 | #[cfg(unix)] 34 | assert!(current.get_native_id().is_err()); 35 | #[cfg(windows)] 36 | assert!(current.get_native_id().is_err()); 37 | }); 38 | another_thread.join().unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # Copied from Twilight's Lint workflow. 2 | # 3 | # https://github.com/twilight-rs/twilight/blob/trunk/.github/workflows/lint.yml 4 | name: Lint 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | clippy: 10 | name: Clippy 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout sources 15 | uses: actions/checkout@v4 16 | 17 | - name: Install stable toolchain 18 | id: toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: nightly 22 | components: clippy 23 | profile: minimal 24 | override: true 25 | 26 | - name: Setup cache 27 | uses: actions/cache@v4 28 | with: 29 | path: | 30 | ~/.cargo/registry 31 | ~/.cargo/git 32 | target 33 | key: ${{ runner.os }}-clippy-rustc-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} 34 | 35 | - name: Run clippy 36 | uses: actions-rs/clippy-check@v1 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | args: --workspace --tests 40 | 41 | rustfmt: 42 | name: Format 43 | runs-on: ubuntu-latest 44 | 45 | steps: 46 | - name: Checkout sources 47 | uses: actions/checkout@v4 48 | 49 | - name: Install stable toolchain 50 | uses: actions-rs/toolchain@v1 51 | with: 52 | toolchain: nightly 53 | components: rustfmt 54 | profile: minimal 55 | override: true 56 | 57 | - name: Run cargo fmt 58 | run: cargo fmt --all -- --check 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # thread-priority 2 | [![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) 3 | 4 | 5 | A simple library to control thread schedule policies and thread priority. 6 | 7 | If your operating system isn't yet supported, please, create an issue. 8 | 9 | ## Minimal Rust Compiler Version 10 | Is `1.67.1` for < `2` and `1.85` for version `2`. If you need any help making it possible to compile 11 | with `1.56` please reach out, it should be possible by just downgrading the `rstest` version to 12 | `0.17` or lower (the code is compatible). 13 | 14 | ## Supported platforms 15 | - Linux 16 | - Android 17 | - DragonFly 18 | - FreeBSD 19 | - OpenBSD 20 | - NetBSD 21 | - macOS 22 | - iOS 23 | - Windows 24 | 25 | ## Examples 26 | 27 | ### Minimal cross-platform examples 28 | Setting current thread's priority to minimum: 29 | 30 | ```rust,no_run 31 | use thread_priority::*; 32 | 33 | fn main() { 34 | assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); 35 | } 36 | ``` 37 | 38 | The same as above but using a specific value: 39 | 40 | ```rust,no_run 41 | use thread_priority::*; 42 | use std::convert::TryInto; 43 | 44 | fn main() { 45 | // The lower the number the lower the priority. 46 | assert!(set_current_thread_priority(ThreadPriority::Crossplatform(0.try_into().unwrap())).is_ok()); 47 | } 48 | ``` 49 | 50 | ### Windows-specific examples 51 | Set the thread priority to the lowest possible value: 52 | 53 | ```rust,no_run 54 | use thread_priority::*; 55 | 56 | fn main() { 57 | // The lower the number the lower the priority. 58 | assert!(set_current_thread_priority(ThreadPriority::Os(WinAPIThreadPriority::Lowest.into())).is_ok()); 59 | } 60 | ``` 61 | 62 | Set the ideal processor for the new thread: 63 | 64 | ```rust,no_run 65 | use thread_priority::*; 66 | 67 | fn main() { 68 | std::thread::spawn(|| { 69 | set_thread_ideal_processor(thread_native_id(), 0); 70 | println!("Hello world!"); 71 | }); 72 | } 73 | ``` 74 | 75 | 76 | ### Building a thread using the ThreadBuilderExt trait 77 | 78 | ```rust,no_run 79 | use thread_priority::*; 80 | use thread_priority::ThreadBuilderExt; 81 | 82 | let thread = std::thread::Builder::new() 83 | .name("MyNewThread".to_owned()) 84 | .spawn_with_priority(ThreadPriority::Max, |result| { 85 | // This is printed out from within the spawned thread. 86 | println!("Set priority result: {:?}", result); 87 | assert!(result.is_ok()); 88 | }).unwrap(); 89 | thread.join(); 90 | ``` 91 | 92 | ### Building a thread using the ThreadBuilder. 93 | 94 | ```rust,no_run 95 | use thread_priority::*; 96 | 97 | let thread = ThreadBuilder::default() 98 | .name("MyThread") 99 | .priority(ThreadPriority::Max) 100 | .spawn(|result| { 101 | // This is printed out from within the spawned thread. 102 | println!("Set priority result: {:?}", result); 103 | assert!(result.is_ok()); 104 | }).unwrap(); 105 | thread.join(); 106 | 107 | // Another example where we don't care about the priority having been set. 108 | let thread = ThreadBuilder::default() 109 | .name("MyThread") 110 | .priority(ThreadPriority::Max) 111 | .spawn_careless(|| { 112 | // This is printed out from within the spawned thread. 113 | println!("We don't care about the priority result."); 114 | }).unwrap(); 115 | thread.join(); 116 | ``` 117 | 118 | ### Using ThreadExt trait on the current thread 119 | 120 | ```rust,no_run 121 | use thread_priority::*; 122 | 123 | assert!(std::thread::current().get_priority().is_ok()); 124 | println!("This thread's native id is: {:?}", std::thread::current().get_native_id()); 125 | ``` 126 | 127 | ## License 128 | This project is [licensed under the MIT license](https://github.com/iddm/thread-priority/blob/master/LICENSE). 129 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | minrust: 1.85 7 | 8 | jobs: 9 | test: 10 | name: Test 11 | runs-on: ${{ matrix.os || 'ubuntu-latest' }} 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | name: 17 | - stable 18 | - beta 19 | - nightly 20 | - macOS 21 | - Windows 22 | 23 | include: 24 | - name: beta 25 | toolchain: beta 26 | - name: nightly 27 | toolchain: nightly 28 | - name: macOS 29 | os: macOS-latest 30 | - name: Windows 31 | os: windows-latest 32 | 33 | steps: 34 | - name: Checkout sources 35 | uses: actions/checkout@v4 36 | 37 | - name: Install toolchain 38 | id: tc 39 | uses: actions-rs/toolchain@v1 40 | with: 41 | toolchain: ${{ matrix.toolchain || 'stable' }} 42 | profile: minimal 43 | override: true 44 | 45 | - name: Setup cache 46 | if: runner.os != 'macOS' 47 | uses: actions/cache@v4 48 | with: 49 | path: | 50 | ~/.cargo/registry 51 | ~/.cargo/git 52 | target 53 | key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 54 | 55 | - name: Build all features 56 | if: matrix.features == '' 57 | run: cargo build --all-features 58 | 59 | - name: Test all features (macos) 60 | if: matrix.features == '' && runner.os == 'macOS' 61 | run: sudo -E /Users/runner/.cargo/bin/cargo test --all-features 62 | 63 | - name: Test all features (Windows) 64 | if: matrix.features == '' && runner.os == 'Windows' 65 | run: cargo test --all-features 66 | 67 | - name: Test all features (non-linux and non-macOS) 68 | if: matrix.features == '' && runner.os != 'Linux' && runner.os != 'macOS' && runner.os != 'Windows' 69 | run: sudo -E /home/runner/.cargo/bin/cargo test --all-features -- --skip set_deadline_policy 70 | 71 | - name: Test all features (Linux) 72 | if: matrix.features == '' && runner.os == 'Linux' 73 | run: sudo -E /home/runner/.cargo/bin/cargo test --all-features 74 | # run: sudo -E /usr/share/rust/.cargo/bin/cargo test --all-features 75 | 76 | clippy: 77 | name: Run clippy 78 | runs-on: ubuntu-latest 79 | 80 | steps: 81 | - name: Checkout sources 82 | uses: actions/checkout@v4 83 | 84 | - name: Install toolchain 85 | id: tc 86 | uses: actions-rs/toolchain@v1 87 | with: 88 | toolchain: stable 89 | profile: minimal 90 | override: true 91 | 92 | - name: Setup cache 93 | uses: actions/cache@v4 94 | with: 95 | path: | 96 | ~/.cargo/registry 97 | ~/.cargo/git 98 | target 99 | key: ${{ runner.os }}-clippy-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 100 | 101 | - run: cargo clippy 102 | 103 | MSRV: 104 | runs-on: ubuntu-latest 105 | 106 | steps: 107 | - name: Checkout sources 108 | uses: actions/checkout@v4 109 | 110 | - name: Install toolchain (${{ env.minrust }}) 111 | id: tc 112 | uses: actions-rs/toolchain@v1 113 | with: 114 | toolchain: ${{ env.minrust }} 115 | profile: minimal 116 | override: true 117 | 118 | - name: Setup cache 119 | uses: actions/cache@v4 120 | with: 121 | path: | 122 | ~/.cargo/registry 123 | ~/.cargo/git 124 | target 125 | key: ${{ runner.os }}-msrv-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 126 | 127 | - run: cargo check --all-features 128 | 129 | doc: 130 | name: Build docs 131 | runs-on: ubuntu-latest 132 | 133 | steps: 134 | - name: Checkout sources 135 | uses: actions/checkout@v4 136 | 137 | - name: Install toolchain 138 | id: tc 139 | uses: actions-rs/toolchain@v1 140 | with: 141 | toolchain: nightly 142 | profile: minimal 143 | override: true 144 | 145 | - name: Setup cache 146 | uses: actions/cache@v4 147 | with: 148 | path: | 149 | ~/.cargo/registry 150 | ~/.cargo/git 151 | key: ${{ runner.os }}-docs-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 152 | 153 | - name: Build docs 154 | run: | 155 | cargo doc 156 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Build 2 | 3 | on: 4 | schedule: 5 | - cron: '0 2 * * *' 6 | 7 | env: 8 | minrust: 1.85 9 | 10 | jobs: 11 | test: 12 | name: Test 13 | runs-on: ${{ matrix.os || 'ubuntu-latest' }} 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | name: 19 | - stable 20 | - beta 21 | - nightly 22 | - macOS 23 | - Windows 24 | 25 | include: 26 | - name: beta 27 | toolchain: beta 28 | - name: nightly 29 | toolchain: nightly 30 | - name: macOS 31 | os: macOS-latest 32 | - name: Windows 33 | os: windows-latest 34 | 35 | steps: 36 | - name: Checkout sources 37 | uses: actions/checkout@v4 38 | 39 | - name: Install toolchain 40 | id: tc 41 | uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: ${{ matrix.toolchain || 'stable' }} 44 | profile: minimal 45 | override: true 46 | 47 | - name: Setup cache 48 | uses: actions/cache@v4 49 | with: 50 | path: | 51 | ~/.cargo/registry 52 | ~/.cargo/git 53 | target 54 | key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 55 | 56 | - name: Build all features 57 | if: matrix.features == '' 58 | run: cargo build --all-features 59 | 60 | - name: Test all features (other) 61 | if: matrix.features == '' && runner.os != 'Linux' 62 | run: cargo test --all-features -- --skip set_deadline_policy 63 | 64 | - name: Test all features (Linux) 65 | if: matrix.features == '' && runner.os == 'Linux' 66 | run: sudo -E /home/runner/.cargo/bin/cargo test --all-features 67 | # run: sudo -E /usr/share/rust/.cargo/bin/cargo test --all-features 68 | 69 | rustfmt: 70 | name: Format 71 | runs-on: ubuntu-latest 72 | 73 | steps: 74 | - name: Checkout sources 75 | uses: actions/checkout@v4 76 | 77 | - name: Install stable toolchain 78 | uses: actions-rs/toolchain@v1 79 | with: 80 | toolchain: nightly 81 | components: rustfmt 82 | profile: minimal 83 | override: true 84 | 85 | - name: Run cargo fmt 86 | run: cargo fmt --all -- --check 87 | 88 | clippy: 89 | name: Clippy 90 | runs-on: ubuntu-latest 91 | 92 | steps: 93 | - name: Checkout sources 94 | uses: actions/checkout@v4 95 | 96 | - name: Install toolchain 97 | id: tc 98 | uses: actions-rs/toolchain@v1 99 | with: 100 | toolchain: stable 101 | profile: minimal 102 | override: true 103 | 104 | - name: Setup cache 105 | uses: actions/cache@v4 106 | with: 107 | path: | 108 | ~/.cargo/registry 109 | ~/.cargo/git 110 | target 111 | key: ${{ runner.os }}-clippy-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 112 | 113 | - run: cargo clippy 114 | 115 | MSRV: 116 | runs-on: ubuntu-latest 117 | 118 | steps: 119 | - name: Checkout sources 120 | uses: actions/checkout@v4 121 | 122 | - name: Install toolchain (${{ env.minrust }}) 123 | id: tc 124 | uses: actions-rs/toolchain@v1 125 | with: 126 | toolchain: ${{ env.minrust }} 127 | profile: minimal 128 | override: true 129 | 130 | - name: Setup cache 131 | uses: actions/cache@v4 132 | with: 133 | path: | 134 | ~/.cargo/registry 135 | ~/.cargo/git 136 | target 137 | key: ${{ runner.os }}-msrv-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 138 | 139 | - run: cargo check --all-features 140 | 141 | doc: 142 | name: Build docs 143 | runs-on: ubuntu-latest 144 | 145 | steps: 146 | - name: Checkout sources 147 | uses: actions/checkout@v4 148 | 149 | - name: Install toolchain 150 | id: tc 151 | uses: actions-rs/toolchain@v1 152 | with: 153 | toolchain: nightly 154 | profile: minimal 155 | override: true 156 | 157 | - name: Setup cache 158 | uses: actions/cache@v4 159 | with: 160 | path: | 161 | ~/.cargo/registry 162 | ~/.cargo/git 163 | key: ${{ runner.os }}-docs-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} 164 | 165 | - name: Build docs 166 | run: | 167 | cargo doc 168 | -------------------------------------------------------------------------------- /tests/unix.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(windows))] 2 | 3 | use rstest::rstest; 4 | use std::convert::TryInto; 5 | use thread_priority::*; 6 | 7 | #[cfg(target_os = "linux")] 8 | #[test] 9 | fn get_and_set_priority_with_normal_and_crossplatform() { 10 | let nice = unsafe { libc::getpriority(0, 0) }; 11 | assert_eq!(nice, 0); 12 | crate::set_current_thread_priority(ThreadPriority::Crossplatform(30u8.try_into().unwrap())) 13 | .unwrap(); 14 | let nice = unsafe { libc::getpriority(0, 0) }; 15 | assert!(nice > 0); 16 | // Note that increasing priority requires extra permissions (e.g. sudo) 17 | crate::set_current_thread_priority(ThreadPriority::Crossplatform(70u8.try_into().unwrap())) 18 | .unwrap(); 19 | let nice = unsafe { libc::getpriority(0, 0) }; 20 | assert!(nice < 0); 21 | } 22 | 23 | #[cfg(target_os = "linux")] 24 | #[rstest] 25 | fn get_and_set_priority_with_normal_policies( 26 | #[values( 27 | ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other), 28 | ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle), 29 | ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Batch) 30 | )] 31 | policy: ThreadSchedulePolicy, 32 | #[values(ThreadPriority::Min, ThreadPriority::Max, ThreadPriority::Crossplatform(23u8.try_into().unwrap()))] 33 | priority: ThreadPriority, 34 | ) { 35 | let ret = set_thread_priority_and_policy(thread_native_id(), priority, policy); 36 | if policy == ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle) 37 | && priority == ThreadPriority::Crossplatform(23u8.try_into().unwrap()) 38 | { 39 | assert_eq!(ret, Err(Error::PriorityNotInRange(0..=0))); 40 | } else { 41 | assert!(ret.is_ok()); 42 | } 43 | } 44 | 45 | // In macOS it is allowed to specify number as a SCHED_OTHER policy priority. 46 | #[cfg(any( 47 | target_os = "macos", 48 | target_os = "openbsd", 49 | target_os = "vxworks", 50 | target_os = "freebsd", 51 | target_os = "netbsd" 52 | ))] 53 | #[rstest] 54 | fn get_and_set_priority_with_normal_policies( 55 | #[values(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other))] 56 | policy: ThreadSchedulePolicy, 57 | #[values(ThreadPriority::Min, ThreadPriority::Max, ThreadPriority::Crossplatform(23u8.try_into().unwrap()))] 58 | priority: ThreadPriority, 59 | ) { 60 | assert!(set_thread_priority_and_policy(thread_native_id(), priority, policy).is_ok()); 61 | } 62 | 63 | #[rstest] 64 | #[cfg(target_os = "linux")] 65 | #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle), 0..=0)] 66 | #[cfg(target_os = "linux")] 67 | #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Batch), -20..=19)] 68 | #[cfg(not(target_os = "vxworks"))] 69 | #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other), -20..=19)] 70 | #[cfg(not(target_os = "vxworks"))] 71 | #[case(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo), 0..=99)] 72 | #[cfg(not(target_os = "vxworks"))] 73 | #[case(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin), 0..=99)] 74 | #[cfg(target_os = "vxworks")] 75 | #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other), 0.=255)] 76 | #[cfg(target_os = "vxworks")] 77 | #[case(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo), 0..=255)] 78 | #[cfg(target_os = "vxworks")] 79 | #[case(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin), 0..=255)] 80 | #[cfg(target_os = "vxworks")] 81 | #[case(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Sporadic), 0..=255)] 82 | fn check_min_and_max_priority_values( 83 | #[case] policy: ThreadSchedulePolicy, 84 | #[case] posix_range: std::ops::RangeInclusive, 85 | ) { 86 | let max_value = ThreadPriority::max_value_for_policy(policy).unwrap(); 87 | let min_value = ThreadPriority::min_value_for_policy(policy).unwrap(); 88 | assert!(posix_range.contains(&max_value)); 89 | assert!(posix_range.contains(&min_value)); 90 | } 91 | 92 | #[rstest] 93 | #[cfg(target_os = "linux")] 94 | #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle))] 95 | #[cfg(target_os = "linux")] 96 | #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Batch))] 97 | #[case(ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other))] 98 | fn set_priority_with_normal_policy_but_with_invalid_value(#[case] policy: ThreadSchedulePolicy) { 99 | let thread_id = thread_native_id(); 100 | #[cfg(target_os = "linux")] 101 | let expected = if policy == ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Idle) { 102 | // In Linux we should get an error whenever a non-zero value is passed as priority and a normal 103 | // scheduling policy is used. 104 | Err(Error::PriorityNotInRange(0..=0)) 105 | } else { 106 | Ok(()) 107 | }; 108 | 109 | assert_eq!( 110 | set_thread_priority_and_policy( 111 | thread_id, 112 | ThreadPriority::Crossplatform(23u8.try_into().unwrap()), 113 | policy, 114 | ), 115 | expected 116 | ); 117 | } 118 | 119 | #[cfg(any( 120 | target_os = "macos", 121 | target_os = "openbsd", 122 | target_os = "vxworks", 123 | target_os = "freebsd", 124 | target_os = "netbsd" 125 | ))] 126 | #[test] 127 | // In macOS the SCHED_OTHER policy allows having a non-zero priority value, 128 | // but the crate doesn't use this opportunity for normal threads and uses niceness instead. 129 | fn get_and_set_priority_with_normal_policy() { 130 | let thread_id = thread_native_id(); 131 | let normal_policy = ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other); 132 | 133 | assert_eq!( 134 | set_thread_priority_and_policy( 135 | thread_id, 136 | ThreadPriority::Crossplatform(23u8.try_into().unwrap()), 137 | normal_policy, 138 | ), 139 | Ok(()) 140 | ); 141 | assert_eq!(thread_schedule_policy(), Ok(normal_policy)); 142 | } 143 | 144 | #[rstest] 145 | #[case::fifo(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo))] 146 | #[case::roundrobin(ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin))] 147 | fn get_and_set_priority_with_realtime_policy_requires_capabilities( 148 | #[case] realtime_policy: ThreadSchedulePolicy, 149 | ) { 150 | let thread_id = thread_native_id(); 151 | let max_value = ThreadPriority::max_value_for_policy(realtime_policy).unwrap(); 152 | let min_value = ThreadPriority::min_value_for_policy(realtime_policy).unwrap(); 153 | 154 | assert_eq!( 155 | set_thread_priority_and_policy(thread_id, ThreadPriority::Max, realtime_policy,), 156 | Ok(()) 157 | ); 158 | assert_eq!(thread_schedule_policy(), Ok(realtime_policy)); 159 | assert_eq!( 160 | thread_schedule_policy_param(thread_native_id()), 161 | Ok(( 162 | realtime_policy, 163 | ScheduleParams { 164 | sched_priority: max_value 165 | } 166 | )) 167 | ); 168 | assert_eq!( 169 | Thread::current(), 170 | Ok(Thread { 171 | priority: ThreadPriority::Crossplatform((max_value as u8).try_into().unwrap()), 172 | id: thread_native_id() 173 | }) 174 | ); 175 | 176 | assert_eq!( 177 | set_thread_priority_and_policy( 178 | thread_id, 179 | ThreadPriority::Crossplatform(23u8.try_into().unwrap()), 180 | realtime_policy, 181 | ), 182 | Ok(()) 183 | ); 184 | assert_eq!(thread_schedule_policy(), Ok(realtime_policy)); 185 | assert_eq!( 186 | thread_schedule_policy_param(thread_native_id()), 187 | Ok((realtime_policy, ScheduleParams { sched_priority: 23 })) 188 | ); 189 | assert_eq!( 190 | Thread::current(), 191 | Ok(Thread { 192 | priority: ThreadPriority::Crossplatform(23u8.try_into().unwrap()), 193 | id: thread_native_id() 194 | }) 195 | ); 196 | 197 | assert_eq!( 198 | set_thread_priority_and_policy(thread_id, ThreadPriority::Min, realtime_policy,), 199 | Ok(()) 200 | ); 201 | assert_eq!(thread_schedule_policy(), Ok(realtime_policy)); 202 | assert_eq!( 203 | thread_schedule_policy_param(thread_native_id()), 204 | Ok(( 205 | realtime_policy, 206 | ScheduleParams { 207 | sched_priority: min_value 208 | } 209 | )) 210 | ); 211 | assert_eq!( 212 | Thread::current(), 213 | Ok(Thread { 214 | priority: ThreadPriority::Crossplatform((min_value as u8).try_into().unwrap()), 215 | id: thread_native_id() 216 | }) 217 | ); 218 | } 219 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the windows thread control. 2 | //! 3 | //! The crate's prelude doesn't have much control over 4 | //! the windows threads, and this module provides 5 | //! better control over those. 6 | 7 | use windows::Win32::{ 8 | Foundation::{GetLastError, HANDLE}, 9 | System::{ 10 | Threading::{ 11 | GetCurrentThread, GetThreadPriority, SetThreadIdealProcessor, SetThreadPriority, 12 | SetThreadPriorityBoost, THREAD_MODE_BACKGROUND_BEGIN, THREAD_MODE_BACKGROUND_END, 13 | THREAD_PRIORITY, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_BELOW_NORMAL, 14 | THREAD_PRIORITY_HIGHEST, THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST, 15 | THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_TIME_CRITICAL, 16 | }, 17 | WindowsProgramming::THREAD_PRIORITY_ERROR_RETURN, 18 | }, 19 | }; 20 | 21 | use crate::{Error, ThreadPriority}; 22 | 23 | /// An alias type for specifying the ideal processor. 24 | /// Used in the WinAPI for affinity control. 25 | pub type IdealProcessor = u32; 26 | 27 | /// An alias type for a thread id. 28 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 29 | #[repr(transparent)] 30 | pub struct ThreadId(HANDLE); 31 | 32 | impl PartialOrd for ThreadId { 33 | fn partial_cmp(&self, other: &Self) -> Option { 34 | Some(self.cmp(other)) 35 | } 36 | } 37 | 38 | impl Ord for ThreadId { 39 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 40 | (self.0.0 as usize).cmp(&(other.0.0 as usize)) 41 | } 42 | } 43 | 44 | impl ThreadId { 45 | /// Returns true if the thread id is invalid. 46 | pub fn is_invalid(&self) -> bool { 47 | self.0.is_invalid() 48 | } 49 | } 50 | 51 | /// The WinAPI priority representation. Check out MSDN for more info: 52 | /// 53 | #[repr(i32)] 54 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 55 | pub enum WinAPIThreadPriority { 56 | /// Begin background processing mode. The system lowers the resource 57 | /// scheduling priorities of the thread so that it can perform background 58 | /// work without significantly affecting activity in the foreground. 59 | /// 60 | /// This value can be specified only if hThread is a handle to the current 61 | /// thread. The function fails if the thread is already in background processing mode. 62 | /// 63 | /// # Warning 64 | /// Windows Server 2003: This value is not supported. 65 | BackgroundModeBegin = THREAD_MODE_BACKGROUND_BEGIN.0, 66 | /// End background processing mode. The system restores the resource 67 | /// scheduling priorities of the thread as they were before the thread 68 | /// entered background processing mode. 69 | /// 70 | /// This value can be specified only if hThread is a handle to the current thread. 71 | /// The function fails if the thread is not in background processing mode. 72 | /// 73 | /// # Warning 74 | /// Windows Server 2003: This value is not supported. 75 | BackgroundModeEnd = THREAD_MODE_BACKGROUND_END.0, 76 | /// Priority 1 point above the priority class. 77 | AboveNormal = THREAD_PRIORITY_ABOVE_NORMAL.0, 78 | /// Priority 1 point below the priority class. 79 | BelowNormal = THREAD_PRIORITY_BELOW_NORMAL.0, 80 | /// Priority 2 points above the priority class. 81 | Highest = THREAD_PRIORITY_HIGHEST.0, 82 | /// Base priority of 1 for IDLE_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, 83 | /// NORMAL_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS 84 | /// processes, and a base priority of 16 for REALTIME_PRIORITY_CLASS processes. 85 | Idle = THREAD_PRIORITY_IDLE.0, 86 | /// Priority 2 points below the priority class. 87 | Lowest = THREAD_PRIORITY_LOWEST.0, 88 | /// Normal priority for the priority class. 89 | Normal = THREAD_PRIORITY_NORMAL.0, 90 | /// Base priority of 15 for IDLE_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, 91 | /// NORMAL_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS 92 | /// processes, and a base priority of 31 for REALTIME_PRIORITY_CLASS processes. 93 | TimeCritical = THREAD_PRIORITY_TIME_CRITICAL.0, 94 | } 95 | 96 | impl std::convert::TryFrom for WinAPIThreadPriority { 97 | type Error = crate::Error; 98 | 99 | fn try_from(priority: ThreadPriority) -> Result { 100 | Ok(match priority { 101 | ThreadPriority::Min => WinAPIThreadPriority::Lowest, 102 | ThreadPriority::Crossplatform(crate::ThreadPriorityValue(p)) => match p { 103 | 0 => WinAPIThreadPriority::Idle, 104 | 1..=19 => WinAPIThreadPriority::Lowest, 105 | 20..=39 => WinAPIThreadPriority::BelowNormal, 106 | 40..=59 => WinAPIThreadPriority::Normal, 107 | 60..=79 => WinAPIThreadPriority::AboveNormal, 108 | 80..=98 => WinAPIThreadPriority::Highest, 109 | 99 => WinAPIThreadPriority::TimeCritical, 110 | _ => return Err(Error::Priority("The value is out of range [0; 99].")), 111 | }, 112 | ThreadPriority::Os(crate::ThreadPriorityOsValue(p)) => { 113 | match THREAD_PRIORITY(p as i32) { 114 | THREAD_MODE_BACKGROUND_BEGIN => WinAPIThreadPriority::BackgroundModeBegin, 115 | THREAD_MODE_BACKGROUND_END => WinAPIThreadPriority::BackgroundModeEnd, 116 | THREAD_PRIORITY_ABOVE_NORMAL => WinAPIThreadPriority::AboveNormal, 117 | THREAD_PRIORITY_BELOW_NORMAL => WinAPIThreadPriority::BelowNormal, 118 | THREAD_PRIORITY_HIGHEST => WinAPIThreadPriority::Highest, 119 | THREAD_PRIORITY_IDLE => WinAPIThreadPriority::Idle, 120 | THREAD_PRIORITY_LOWEST => WinAPIThreadPriority::Lowest, 121 | THREAD_PRIORITY_NORMAL => WinAPIThreadPriority::Normal, 122 | THREAD_PRIORITY_TIME_CRITICAL => WinAPIThreadPriority::TimeCritical, 123 | _ => { 124 | return Err(Error::Priority( 125 | "The value is out of range of allowed values.", 126 | )); 127 | } 128 | } 129 | } 130 | ThreadPriority::Max => WinAPIThreadPriority::Highest, 131 | }) 132 | } 133 | } 134 | 135 | impl std::convert::TryFrom for WinAPIThreadPriority { 136 | type Error = crate::Error; 137 | 138 | fn try_from(priority: THREAD_PRIORITY) -> Result { 139 | Ok(match priority { 140 | THREAD_MODE_BACKGROUND_BEGIN => WinAPIThreadPriority::BackgroundModeBegin, 141 | THREAD_MODE_BACKGROUND_END => WinAPIThreadPriority::BackgroundModeEnd, 142 | THREAD_PRIORITY_ABOVE_NORMAL => WinAPIThreadPriority::AboveNormal, 143 | THREAD_PRIORITY_BELOW_NORMAL => WinAPIThreadPriority::BelowNormal, 144 | THREAD_PRIORITY_HIGHEST => WinAPIThreadPriority::Highest, 145 | THREAD_PRIORITY_IDLE => WinAPIThreadPriority::Idle, 146 | THREAD_PRIORITY_LOWEST => WinAPIThreadPriority::Lowest, 147 | THREAD_PRIORITY_NORMAL => WinAPIThreadPriority::Normal, 148 | THREAD_PRIORITY_TIME_CRITICAL => WinAPIThreadPriority::TimeCritical, 149 | _ => return Err(Error::Priority("Priority couldn't be parsed")), 150 | }) 151 | } 152 | } 153 | 154 | impl From for crate::ThreadPriorityOsValue { 155 | fn from(p: WinAPIThreadPriority) -> Self { 156 | crate::ThreadPriorityOsValue(p as u32) 157 | } 158 | } 159 | 160 | /// Sets thread's priority and schedule policy. 161 | /// 162 | /// * May require privileges 163 | /// 164 | /// # Usage 165 | /// 166 | /// Setting thread priority to minimum: 167 | /// 168 | /// ```rust 169 | /// use thread_priority::*; 170 | /// 171 | /// let thread_id = thread_native_id(); 172 | /// assert!(set_thread_priority(thread_id, ThreadPriority::Min).is_ok()); 173 | /// ``` 174 | /// 175 | /// If there's an error, a result of 176 | /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. 177 | pub fn set_thread_priority(native: ThreadId, priority: ThreadPriority) -> Result<(), Error> { 178 | set_winapi_thread_priority(native, WinAPIThreadPriority::try_from(priority)?) 179 | } 180 | 181 | /// Sets thread's priority and schedule policy using WinAPI priority values. 182 | /// 183 | /// * May require privileges 184 | /// 185 | /// # Usage 186 | /// 187 | /// Setting thread priority to minimum: 188 | /// 189 | /// ```rust 190 | /// use thread_priority::*; 191 | /// 192 | /// let thread_id = thread_native_id(); 193 | /// assert!(set_winapi_thread_priority(thread_id, WinAPIThreadPriority::Normal).is_ok()); 194 | /// ``` 195 | /// 196 | /// If there's an error, a result of 197 | /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. 198 | pub fn set_winapi_thread_priority( 199 | native: ThreadId, 200 | priority: WinAPIThreadPriority, 201 | ) -> Result<(), Error> { 202 | unsafe { 203 | SetThreadPriority(native.0, THREAD_PRIORITY(priority as i32)) 204 | .map_err(|e| Error::OS(e.code().0)) 205 | } 206 | } 207 | 208 | /// Set current thread's priority. 209 | /// 210 | /// * May require privileges 211 | /// 212 | /// # Usage 213 | /// 214 | /// Setting thread priority to minimum: 215 | /// 216 | /// ```rust 217 | /// use thread_priority::*; 218 | /// 219 | /// assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); 220 | /// assert!(set_current_thread_priority(ThreadPriority::Os(WinAPIThreadPriority::Lowest.into())).is_ok()); 221 | /// ``` 222 | /// 223 | /// If there's an error, a result of 224 | /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. 225 | pub fn set_current_thread_priority(priority: ThreadPriority) -> Result<(), Error> { 226 | let thread_id = thread_native_id(); 227 | set_thread_priority(thread_id, priority) 228 | } 229 | 230 | /// Get the thread's priority value. 231 | /// 232 | /// Returns current thread's priority. 233 | /// 234 | /// # Usage 235 | /// 236 | /// ```rust 237 | /// use thread_priority::{thread_native_id, get_thread_priority}; 238 | /// 239 | /// assert!(get_thread_priority(thread_native_id()).is_ok()); 240 | /// ``` 241 | pub fn get_thread_priority(native: ThreadId) -> Result { 242 | unsafe { 243 | let ret = GetThreadPriority(native.0); 244 | if ret as u32 != THREAD_PRIORITY_ERROR_RETURN { 245 | Ok(ThreadPriority::Os(crate::ThreadPriorityOsValue::from( 246 | WinAPIThreadPriority::try_from(THREAD_PRIORITY(ret))?, 247 | ))) 248 | } else { 249 | Err(Error::OS(GetLastError().0 as i32)) 250 | } 251 | } 252 | } 253 | 254 | /// Get current thread's priority value. 255 | /// 256 | /// Returns current thread's priority. 257 | /// 258 | /// # Usage 259 | /// 260 | /// ```rust 261 | /// use thread_priority::get_current_thread_priority; 262 | /// 263 | /// assert!(get_current_thread_priority().is_ok()); 264 | /// ``` 265 | pub fn get_current_thread_priority() -> Result { 266 | unsafe { 267 | let ret = GetThreadPriority(thread_native_id().0); 268 | if ret as u32 != THREAD_PRIORITY_ERROR_RETURN { 269 | Ok(ThreadPriority::Os(crate::ThreadPriorityOsValue::from( 270 | WinAPIThreadPriority::try_from(THREAD_PRIORITY(ret))?, 271 | ))) 272 | } else { 273 | Err(Error::OS(GetLastError().0 as i32)) 274 | } 275 | } 276 | } 277 | 278 | /// Returns current thread id, which is the current OS's native handle. 279 | /// It may or may not be equal or even related to rust's thread id, 280 | /// there is absolutely no guarantee for that. 281 | /// 282 | /// # Usage 283 | /// 284 | /// ```rust 285 | /// use thread_priority::thread_native_id; 286 | /// 287 | /// assert!(!thread_native_id().is_invalid()); 288 | /// ``` 289 | pub fn thread_native_id() -> ThreadId { 290 | ThreadId(unsafe { GetCurrentThread() }) 291 | } 292 | 293 | /// Disables or enables the ability of the system to temporarily boost the priority of a thread. 294 | /// 295 | /// If there's an error, a result of 296 | /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. 297 | /// 298 | /// # Usage 299 | /// 300 | /// ```rust 301 | /// use thread_priority::*; 302 | /// 303 | /// let thread_id = thread_native_id(); 304 | /// assert!(set_thread_priority_boost(thread_id, false).is_ok()) 305 | /// ``` 306 | pub fn set_thread_priority_boost(native: ThreadId, enabled: bool) -> Result<(), Error> { 307 | unsafe { SetThreadPriorityBoost(native.0, enabled).map_err(|e| Error::OS(e.code().0)) } 308 | } 309 | 310 | /// Disables or enables the ability of the system to temporarily boost the priority of a current thread. 311 | /// 312 | /// If there's an error, a result of 313 | /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. 314 | /// 315 | /// This is a short-hand of the `set_thread_priority_boost` function for the current thread. 316 | pub fn set_current_thread_priority_boost(enabled: bool) -> Result<(), Error> { 317 | set_thread_priority_boost(thread_native_id(), enabled) 318 | } 319 | 320 | /// Sets a preferred processor for a thread. The system schedules threads on their preferred 321 | /// processors whenever possible. 322 | /// 323 | /// On a system with more than 64 processors, this function sets the preferred processor to a 324 | /// logical processor in the processor group to which the calling thread is assigned. Use the 325 | /// `SetThreadIdealProcessorEx` function to specify a processor group and preferred processor. 326 | /// 327 | /// If there's an error, a result of 328 | /// [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) is returned. 329 | /// On success, the function returns a previously assigned processor. 330 | /// 331 | /// # Note 332 | /// The processor number starts with zero. 333 | /// 334 | /// # Usage 335 | /// 336 | /// ```rust 337 | /// use thread_priority::*; 338 | /// 339 | /// let thread_id = thread_native_id(); 340 | /// assert!(set_thread_ideal_processor(thread_id, 0).is_ok()) 341 | /// ``` 342 | pub fn set_thread_ideal_processor( 343 | native: ThreadId, 344 | ideal_processor: IdealProcessor, 345 | ) -> Result { 346 | unsafe { 347 | let ret = SetThreadIdealProcessor(native.0, ideal_processor); 348 | if ret == u32::MAX { 349 | Err(Error::OS(GetLastError().0 as i32)) 350 | } else { 351 | Ok(ret) 352 | } 353 | } 354 | } 355 | 356 | /// Sets a preferred processor for a current thread. The system schedules threads on their preferred 357 | /// processors whenever possible. 358 | /// 359 | /// This is a short-hand of the `set_thread_ideal_processor` function for the current thread. 360 | pub fn set_current_thread_ideal_processor( 361 | ideal_processor: IdealProcessor, 362 | ) -> Result { 363 | set_thread_ideal_processor(thread_native_id(), ideal_processor) 364 | } 365 | 366 | impl std::convert::TryFrom for crate::ThreadPriorityOsValue { 367 | type Error = (); 368 | 369 | fn try_from(value: u32) -> Result { 370 | Ok(crate::ThreadPriorityOsValue( 371 | match THREAD_PRIORITY(value as i32) { 372 | THREAD_MODE_BACKGROUND_BEGIN 373 | | THREAD_MODE_BACKGROUND_END 374 | | THREAD_PRIORITY_ABOVE_NORMAL 375 | | THREAD_PRIORITY_BELOW_NORMAL 376 | | THREAD_PRIORITY_HIGHEST 377 | | THREAD_PRIORITY_IDLE 378 | | THREAD_PRIORITY_LOWEST 379 | | THREAD_PRIORITY_NORMAL 380 | | THREAD_PRIORITY_TIME_CRITICAL => value, 381 | _ => return Err(()), 382 | }, 383 | )) 384 | } 385 | } 386 | 387 | /// Windows-specific complemented part of the [`crate::ThreadExt`] trait. 388 | pub trait ThreadExt { 389 | /// Returns current thread's priority. 390 | /// For more info see [`thread_priority`]. 391 | /// 392 | /// ```rust 393 | /// use thread_priority::*; 394 | /// 395 | /// assert!(std::thread::current().get_priority().is_ok()); 396 | /// ``` 397 | fn get_priority(&self) -> Result { 398 | get_current_thread_priority() 399 | } 400 | 401 | /// Sets current thread's priority. 402 | /// For more info see [`set_current_thread_priority`]. 403 | /// 404 | /// ```rust 405 | /// use thread_priority::*; 406 | /// 407 | /// assert!(std::thread::current().set_priority(ThreadPriority::Min).is_ok()); 408 | /// ``` 409 | fn set_priority(&self, priority: ThreadPriority) -> Result<(), Error> { 410 | set_current_thread_priority(priority) 411 | } 412 | 413 | /// Returns current thread's windows id. 414 | /// For more info see [`thread_native_id`]. 415 | /// 416 | /// ```rust 417 | /// use thread_priority::*; 418 | /// 419 | /// assert!(!std::thread::current().get_native_id().unwrap().is_invalid()); 420 | /// ``` 421 | fn get_native_id(&self) -> Result; 422 | 423 | /// Sets current thread's ideal processor. 424 | /// For more info see [`set_current_thread_ideal_processor`]. 425 | /// 426 | /// ```rust 427 | /// use thread_priority::*; 428 | /// 429 | /// assert!(std::thread::current().set_ideal_processor(0).is_ok()); 430 | /// ``` 431 | fn set_ideal_processor( 432 | &self, 433 | ideal_processor: IdealProcessor, 434 | ) -> Result { 435 | set_current_thread_ideal_processor(ideal_processor) 436 | } 437 | 438 | /// Sets current thread's priority boost. 439 | /// For more info see [`set_current_thread_priority_boost`]. 440 | /// 441 | /// ```rust 442 | /// use thread_priority::*; 443 | /// 444 | /// assert!(std::thread::current().set_priority_boost(true).is_ok()); 445 | /// ``` 446 | fn set_priority_boost(&self, enabled: bool) -> Result<(), Error> { 447 | set_current_thread_priority_boost(enabled) 448 | } 449 | } 450 | 451 | /// Auto-implementation of this trait for the [`std::thread::Thread`]. 452 | impl ThreadExt for std::thread::Thread { 453 | fn get_native_id(&self) -> Result { 454 | if self.id() == std::thread::current().id() { 455 | Ok(thread_native_id()) 456 | } else { 457 | Err(Error::Priority( 458 | "The `ThreadExt::get_native_id()` is currently limited to be called on the current thread.", 459 | )) 460 | } 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Thread priority. A library for changing thread's priority. 2 | //! 3 | //! # Usage 4 | //! 5 | //! Setting thread priority to minimum: 6 | //! 7 | //! ```rust 8 | //! use thread_priority::*; 9 | //! 10 | //! assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); 11 | //! // Or like this: 12 | //! assert!(ThreadPriority::Min.set_for_current().is_ok()); 13 | //! ``` 14 | //! 15 | //! # More examples 16 | //! 17 | //! ### Minimal cross-platform examples 18 | //! Setting current thread's priority to minimum: 19 | //! 20 | //! ```rust,no_run 21 | //! use thread_priority::*; 22 | //! 23 | //! assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); 24 | //! ``` 25 | //! 26 | //! The same as above but using a specific value: 27 | //! 28 | //! ```rust,no_run 29 | //! use thread_priority::*; 30 | //! use std::convert::TryInto; 31 | //! 32 | //! // The lower the number the lower the priority. 33 | //! assert!(set_current_thread_priority(ThreadPriority::Crossplatform(0.try_into().unwrap())).is_ok()); 34 | //! ``` 35 | //! 36 | //! ### Building a thread using the [`ThreadBuilderExt`] trait 37 | //! 38 | //! ```rust,no_run 39 | //! use thread_priority::*; 40 | //! use thread_priority::ThreadBuilderExt; 41 | //! 42 | //! let thread = std::thread::Builder::new() 43 | //! .name("MyNewThread".to_owned()) 44 | //! .spawn_with_priority(ThreadPriority::Max, |result| { 45 | //! // This is printed out from within the spawned thread. 46 | //! println!("Set priority result: {:?}", result); 47 | //! assert!(result.is_ok()); 48 | //! }).unwrap(); 49 | //! thread.join(); 50 | //! 51 | //! // This also support scoped thread. 52 | //! let x = 0; 53 | //! std::thread::scope(|s|{ 54 | //! std::thread::Builder::new() 55 | //! .name("MyNewThread".to_owned()) 56 | //! .spawn_scoped_with_priority(s, ThreadPriority::Max, |result| { 57 | //! // This is printed out from within the spawned thread. 58 | //! println!("Set priority result: {:?}", result); 59 | //! assert!(result.is_ok()); 60 | //! dbg!(&x); 61 | //! }).unwrap(); 62 | //! }); 63 | //! ``` 64 | //! 65 | //! ### Building a thread using the [`ThreadScopeExt`] trait 66 | //! 67 | //! ```rust,no_run 68 | //! use thread_priority::*; 69 | //! let x = 0; 70 | //! std::thread::scope(|s|{ 71 | //! s.spawn_with_priority(ThreadPriority::Max, |result| { 72 | //! // This is printed out from within the spawned thread. 73 | //! println!("Set priority result: {:?}", result); 74 | //! assert!(result.is_ok()); 75 | //! dbg!(&x); 76 | //! }); 77 | //! }); 78 | //! ``` 79 | //! 80 | //! ### Building a thread using the [`ThreadBuilder`]. 81 | //! 82 | //! ```rust,no_run 83 | //! use thread_priority::*; 84 | //! 85 | //! let thread = ThreadBuilder::default() 86 | //! .name("MyThread") 87 | //! .priority(ThreadPriority::Max) 88 | //! .spawn(|result| { 89 | //! // This is printed out from within the spawned thread. 90 | //! println!("Set priority result: {:?}", result); 91 | //! assert!(result.is_ok()); 92 | //! }).unwrap(); 93 | //! thread.join(); 94 | //! 95 | //! // Another example where we don't care about the priority having been set. 96 | //! let thread = ThreadBuilder::default() 97 | //! .name("MyThread") 98 | //! .priority(ThreadPriority::Max) 99 | //! .spawn_careless(|| { 100 | //! // This is printed out from within the spawned thread. 101 | //! println!("We don't care about the priority result."); 102 | //! }).unwrap(); 103 | //! thread.join(); 104 | //! 105 | //! // Scoped thread is also supported if the compiler version is at least 1.63. 106 | //! let mut x = 0; 107 | //! std::thread::scope(|s|{ 108 | //! let thread = ThreadBuilder::default() 109 | //! .name("MyThread") 110 | //! .priority(ThreadPriority::Max) 111 | //! .spawn_scoped(s, |result| { 112 | //! // This is printed out from within the spawned thread. 113 | //! println!("Set priority result: {:?}", result); 114 | //! assert!(result.is_ok()); 115 | //! x += 1; 116 | //! }).unwrap(); 117 | //! thread.join(); 118 | //! }); 119 | //! assert_eq!(x, 1); 120 | //! 121 | //! // Scoped thread also has a "careless" mode. 122 | //! std::thread::scope(|s|{ 123 | //! let thread = ThreadBuilder::default() 124 | //! .name("MyThread") 125 | //! .priority(ThreadPriority::Max) 126 | //! .spawn_scoped_careless(s, || { 127 | //! // This is printed out from within the spawned thread. 128 | //! println!("We don't care about the priority result."); 129 | //! x += 1; 130 | //! }).unwrap(); 131 | //! thread.join(); 132 | //! }); 133 | //! assert_eq!(x, 2); 134 | //! ``` 135 | //! 136 | //! ### Using [`ThreadExt`] trait on the current thread 137 | //! 138 | //! ```rust,no_run 139 | //! use thread_priority::*; 140 | //! 141 | //! assert!(std::thread::current().get_priority().is_ok()); 142 | //! println!("This thread's native id is: {:?}", std::thread::current().get_native_id()); 143 | //! ``` 144 | //! 145 | #![warn(missing_docs)] 146 | #![deny(warnings)] 147 | 148 | #[cfg(any( 149 | target_os = "linux", 150 | target_os = "macos", 151 | target_os = "ios", 152 | target_os = "dragonfly", 153 | target_os = "freebsd", 154 | target_os = "openbsd", 155 | target_os = "vxworks", 156 | target_os = "netbsd", 157 | target_os = "android", 158 | target_arch = "wasm32", 159 | ))] 160 | pub mod unix; 161 | use std::ops::Deref; 162 | #[cfg(any(target_os = "linux", target_os = "android"))] 163 | use std::time::Duration; 164 | 165 | #[cfg(any( 166 | target_os = "linux", 167 | target_os = "macos", 168 | target_os = "ios", 169 | target_os = "dragonfly", 170 | target_os = "freebsd", 171 | target_os = "openbsd", 172 | target_os = "vxworks", 173 | target_os = "netbsd", 174 | target_os = "android", 175 | target_arch = "wasm32", 176 | ))] 177 | pub use unix::*; 178 | 179 | #[cfg(windows)] 180 | pub mod windows; 181 | #[cfg(windows)] 182 | pub use windows::*; 183 | 184 | /// A error type 185 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 186 | pub enum Error { 187 | /// A value which describes why it is impossible to use such a priority. 188 | Priority(&'static str), 189 | /// Indicates that the priority isn't in range and it should be within the provided range. 190 | /// This may happen on different operating systems following a single standard of API but 191 | /// allowing different priority values for different scheduling policies. 192 | PriorityNotInRange(std::ops::RangeInclusive), 193 | /// Target OS' error type. In most systems it is an integer which 194 | /// later should be used with target OS' API for understanding the value. 195 | /// On Linux there is an integer containing an error code from errno. 196 | /// For Windows it contains a number used in Windows for the same purpose. 197 | OS(i32), 198 | /// FFI failure. 199 | Ffi(&'static str), 200 | } 201 | 202 | impl std::fmt::Display for Error { 203 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 204 | match self { 205 | Error::Priority(s) => write!(f, "unable to set priority: {s}"), 206 | Error::PriorityNotInRange(range) => { 207 | write!(f, "priority must be within the range: {range:?}") 208 | } 209 | Error::OS(i) => write!(f, "the operating system returned error code {i}"), 210 | Error::Ffi(s) => write!(f, "FFI error: {s}"), 211 | } 212 | } 213 | } 214 | 215 | impl std::error::Error for Error {} 216 | 217 | /// Platform-independent thread priority value. 218 | /// Should be in `[0; 100)` range. The higher the number is - the higher 219 | /// the priority. 220 | /// 221 | /// The only way to create such a value is a safe conversion from an 8-bit 222 | /// unsigned integer ([`u8`]): 223 | /// 224 | /// ```rust 225 | /// use thread_priority::*; 226 | /// use std::convert::{TryFrom, TryInto}; 227 | /// 228 | /// // Create the lowest possible priority value. 229 | /// assert!(ThreadPriorityValue::try_from(0u8).is_ok()); 230 | /// // Create it implicitly via `TryInto`: 231 | /// let _priority = ThreadPriority::Crossplatform(0u8.try_into().unwrap()); 232 | /// ``` 233 | /// 234 | /// In case you need to get the raw value out of it, use the `Into` trait: 235 | /// 236 | /// ```rust 237 | /// use thread_priority::*; 238 | /// use std::convert::TryFrom; 239 | /// 240 | /// // Create the lowest possible priority value. 241 | /// let priority = ThreadPriorityValue::try_from(0u8).unwrap(); 242 | /// // Create it implicitly via `TryInto`: 243 | /// let raw_value: u8 = priority.into(); 244 | /// assert_eq!(raw_value, 0); 245 | /// ``` 246 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] 247 | pub struct ThreadPriorityValue(pub(crate) u8); 248 | impl ThreadPriorityValue { 249 | /// The maximum value for a thread priority. 250 | pub const MAX: Self = Self(if cfg!(target_os = "vxworks") { 255 } else { 99 }); 251 | /// The minimum value for a thread priority. 252 | pub const MIN: Self = Self(0); 253 | } 254 | 255 | impl Deref for ThreadPriorityValue { 256 | type Target = u8; 257 | 258 | fn deref(&self) -> &Self::Target { 259 | &self.0 260 | } 261 | } 262 | 263 | impl PartialOrd for ThreadPriorityValue { 264 | fn partial_cmp(&self, other: &u8) -> Option { 265 | self.0.partial_cmp(other) 266 | } 267 | } 268 | 269 | impl PartialOrd for u8 { 270 | fn partial_cmp(&self, other: &ThreadPriorityValue) -> Option { 271 | self.partial_cmp(&other.0) 272 | } 273 | } 274 | 275 | impl PartialEq for ThreadPriorityValue { 276 | fn eq(&self, other: &u8) -> bool { 277 | self.0 == *other 278 | } 279 | } 280 | 281 | impl PartialEq for u8 { 282 | fn eq(&self, other: &ThreadPriorityValue) -> bool { 283 | *self == other.0 284 | } 285 | } 286 | 287 | impl std::fmt::Display for ThreadPriorityValue { 288 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 289 | write!(f, "{}", self.0) 290 | } 291 | } 292 | 293 | impl std::convert::TryFrom for ThreadPriorityValue { 294 | type Error = String; 295 | 296 | fn try_from(value: u8) -> Result { 297 | if (Self::MIN..=Self::MAX).contains(&value) { 298 | Ok(Self(value)) 299 | } else { 300 | Err(format!( 301 | "The value is not in the range of [{}; {}]", 302 | Self::MIN, 303 | Self::MAX 304 | )) 305 | } 306 | } 307 | } 308 | 309 | impl From for u8 { 310 | fn from(value: ThreadPriorityValue) -> Self { 311 | value.0 312 | } 313 | } 314 | 315 | /// Platform-specific thread priority value. 316 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] 317 | pub struct ThreadPriorityOsValue(u32); 318 | 319 | /// Thread priority enumeration. 320 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] 321 | pub enum ThreadPriority { 322 | /// Holds a value representing the minimum possible priority. 323 | #[cfg_attr( 324 | target_os = "windows", 325 | doc = "\ 326 | The [`ThreadPriority::Min`] value is mapped to [`WinAPIThreadPriority::Lowest`] and not 327 | [`WinAPIThreadPriority::Idle`] to avoid unexpected drawbacks. Use the specific value 328 | to set it to [`WinAPIThreadPriority::Idle`] when it is really needed. 329 | " 330 | )] 331 | Min, 332 | /// Holds a platform-independent priority value. 333 | /// Usually used when setting a value, for sometimes it is not possible to map 334 | /// the operating system's priority to this value. 335 | Crossplatform(ThreadPriorityValue), 336 | /// Holds an operating system specific value. If it is not possible to obtain the 337 | /// [`ThreadPriority::Crossplatform`] variant of the value, this is returned instead. 338 | #[cfg_attr( 339 | target_os = "windows", 340 | doc = "\ 341 | The value is matched among possible values in Windows from [`WinAPIThreadPriority::Idle`] till 342 | [`WinAPIThreadPriority::TimeCritical`]. This is due to windows only having from 7 to 9 possible 343 | thread priorities and not `100` as it is allowed to have in the [`ThreadPriority::Crossplatform`] 344 | variant. 345 | " 346 | )] 347 | Os(ThreadPriorityOsValue), 348 | /// Holds scheduling parameters for Deadline scheduling. These are, in order, 349 | /// the nanoseconds for runtime, deadline, and period. Please note that the 350 | /// kernel enforces runtime <= deadline <= period. 351 | /// 352 | /// arrival/wakeup absolute deadline 353 | /// | start time | 354 | /// | | | 355 | /// v v v 356 | /// -----x--------xooooooooooooooooo--------x--------x--- 357 | /// |<-- Runtime ------->| 358 | /// |<----------- Deadline ----------->| 359 | /// |<-------------- Period ------------------->| 360 | #[cfg(any(target_os = "linux", target_os = "android"))] 361 | Deadline { 362 | /// Set this to something larger than the average computation time 363 | /// or to the worst-case computation time for hard real-time tasks. 364 | runtime: Duration, 365 | /// Set this to the relative deadline. 366 | deadline: Duration, 367 | /// Set this to the period of the task. 368 | period: Duration, 369 | /// Deadline flags. 370 | flags: crate::unix::DeadlineFlags, 371 | }, 372 | /// Holds a value representing the maximum possible priority. 373 | /// Should be used with caution, it solely depends on the target 374 | /// os where the program is going to be running on, how it will 375 | /// behave. On some systems, the whole system may become frozen 376 | /// if not used properly. 377 | #[cfg_attr( 378 | target_os = "windows", 379 | doc = "\ 380 | The [`ThreadPriority::Max`] value is mapped to [`WinAPIThreadPriority::Highest`] and not 381 | [`WinAPIThreadPriority::TimeCritical`] to avoid unexpected drawbacks. Use the specific value 382 | to set it to [`WinAPIThreadPriority::TimeCritical`] when it is really needed. 383 | " 384 | )] 385 | Max, 386 | } 387 | 388 | impl ThreadPriority { 389 | /// Sets current thread's priority to this value. 390 | pub fn set_for_current(self) -> Result<(), Error> { 391 | set_current_thread_priority(self) 392 | } 393 | } 394 | 395 | /// Represents an OS thread. 396 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 397 | pub struct Thread { 398 | /// Thread's priority. 399 | pub priority: ThreadPriority, 400 | /// Thread's ID (or handle). 401 | pub id: ThreadId, 402 | } 403 | 404 | impl Thread { 405 | /// Get current thread. 406 | /// 407 | /// # Usage 408 | /// 409 | /// ```rust 410 | /// use thread_priority::*; 411 | /// 412 | /// assert!(Thread::current().is_ok()); 413 | /// ``` 414 | pub fn current() -> Result { 415 | Ok(Thread { 416 | priority: get_current_thread_priority()?, 417 | id: thread_native_id(), 418 | }) 419 | } 420 | } 421 | 422 | /// A wrapper producing a closure where the input priority set result is logged on error, but no other handling is performed 423 | fn careless_wrapper(f: F) -> impl FnOnce(Result<(), Error>) -> T 424 | where 425 | F: FnOnce() -> T + Send, 426 | T: Send, 427 | { 428 | |priority_set_result| { 429 | if let Err(e) = priority_set_result { 430 | log::warn!( 431 | "Couldn't set the priority for the thread with Rust Thread ID {:?} named {:?}: {:?}", 432 | std::thread::current().id(), 433 | std::thread::current().name(), 434 | e, 435 | ); 436 | } 437 | 438 | f() 439 | } 440 | } 441 | 442 | /// A copy of the [`std::thread::Builder`] builder allowing to set priority settings. 443 | /// 444 | /// ```rust 445 | /// use thread_priority::*; 446 | /// 447 | /// let thread = ThreadBuilder::default() 448 | /// .name("MyThread") 449 | /// .priority(ThreadPriority::Max) 450 | /// .spawn(|result| { 451 | /// // This is printed out from within the spawned thread. 452 | /// println!("Set priority result: {:?}", result); 453 | /// assert!(result.is_ok()); 454 | /// }).unwrap(); 455 | /// thread.join(); 456 | /// 457 | /// // Another example where we don't care about the priority having been set. 458 | /// let thread = ThreadBuilder::default() 459 | /// .name("MyThread") 460 | /// .priority(ThreadPriority::Max) 461 | /// .spawn_careless(|| { 462 | /// // This is printed out from within the spawned thread. 463 | /// println!("We don't care about the priority result."); 464 | /// }).unwrap(); 465 | /// thread.join(); 466 | /// ``` 467 | /// 468 | /// If the compiler version is at least 1.63, the scoped thread support is also enabled. 469 | /// 470 | /// ```rust 471 | /// use thread_priority::*; 472 | /// 473 | /// // Scoped thread is also supported if the compiler version is at least 1.63. 474 | /// let mut x = 0; 475 | /// std::thread::scope(|s|{ 476 | /// let thread = ThreadBuilder::default() 477 | /// .name("MyThread") 478 | /// .priority(ThreadPriority::Max) 479 | /// .spawn_scoped(s, |result| { 480 | /// // This is printed out from within the spawned thread. 481 | /// println!("Set priority result: {:?}", result); 482 | /// assert!(result.is_ok()); 483 | /// x += 1; 484 | /// }).unwrap(); 485 | /// thread.join(); 486 | /// }); 487 | /// assert_eq!(x, 1); 488 | /// 489 | /// // Scoped thread also has a "careless" mode. 490 | /// std::thread::scope(|s|{ 491 | /// let thread = ThreadBuilder::default() 492 | /// .name("MyThread") 493 | /// .priority(ThreadPriority::Max) 494 | /// .spawn_scoped_careless(s, || { 495 | /// // This is printed out from within the spawned thread. 496 | /// println!("We don't care about the priority result."); 497 | /// x += 1; 498 | /// }).unwrap(); 499 | /// thread.join(); 500 | /// }); 501 | /// assert_eq!(x, 2); 502 | /// ``` 503 | #[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)] 504 | pub struct ThreadBuilder { 505 | name: Option, 506 | stack_size: Option, 507 | priority: Option, 508 | 509 | #[cfg(unix)] 510 | policy: Option, 511 | 512 | #[cfg(windows)] 513 | winapi_priority: Option, 514 | #[cfg(windows)] 515 | boost_enabled: bool, 516 | #[cfg(windows)] 517 | ideal_processor: Option, 518 | } 519 | 520 | impl ThreadBuilder { 521 | /// Names the thread-to-be. Currently the name is used for identification 522 | /// only in panic messages. 523 | /// 524 | /// The name must not contain null bytes (`\0`). 525 | /// 526 | /// For more information about named threads, see 527 | /// [`std::thread::Builder::name()`]. 528 | pub fn name>(mut self, value: VALUE) -> Self { 529 | self.name = Some(value.into()); 530 | self 531 | } 532 | 533 | /// Sets the size of the stack (in bytes) for the new thread. 534 | /// 535 | /// The actual stack size may be greater than this value if 536 | /// the platform specifies a minimal stack size. 537 | /// 538 | /// For more information about the stack size for threads, see 539 | /// [`std::thread::Builder::stack_size()`]. 540 | pub fn stack_size>(mut self, value: VALUE) -> Self { 541 | self.stack_size = Some(value.into()); 542 | self 543 | } 544 | 545 | /// The thread's custom priority. 546 | /// 547 | /// For more information about the stack size for threads, see 548 | /// [`ThreadPriority`]. 549 | pub fn priority>(mut self, value: VALUE) -> Self { 550 | self.priority = Some(value.into()); 551 | self 552 | } 553 | 554 | /// The thread's unix scheduling policy. 555 | /// 556 | /// For more information, see 557 | /// [`crate::unix::ThreadSchedulePolicy`] and [`crate::unix::set_thread_priority_and_policy`]. 558 | #[cfg(unix)] 559 | pub fn policy>(mut self, value: VALUE) -> Self { 560 | self.policy = Some(value.into()); 561 | self 562 | } 563 | 564 | /// The WinAPI priority representation. 565 | /// 566 | /// For more information, see 567 | /// [`crate::windows::WinAPIThreadPriority`]. 568 | #[cfg(windows)] 569 | pub fn winapi_priority>( 570 | mut self, 571 | value: VALUE, 572 | ) -> Self { 573 | self.winapi_priority = Some(value.into()); 574 | self 575 | } 576 | 577 | /// Disables or enables the ability of the system to temporarily boost the priority of a thread. 578 | /// 579 | /// For more information, see 580 | /// [`crate::windows::set_thread_priority_boost`]. 581 | #[cfg(windows)] 582 | pub fn boost_enabled(mut self, value: bool) -> Self { 583 | self.boost_enabled = value; 584 | self 585 | } 586 | 587 | /// Sets a preferred processor for a thread. The system schedules threads on their preferred 588 | /// processors whenever possible. 589 | /// 590 | /// For more information, see 591 | /// [`crate::windows::set_thread_ideal_processor`]. 592 | #[cfg(windows)] 593 | pub fn ideal_processor>(mut self, value: VALUE) -> Self { 594 | self.ideal_processor = Some(value.into()); 595 | self 596 | } 597 | 598 | #[cfg(unix)] 599 | fn spawn_wrapper(self, f: F) -> impl FnOnce() -> T 600 | where 601 | F: FnOnce(Result<(), Error>) -> T, 602 | F: Send, 603 | T: Send, 604 | { 605 | move || match (self.priority, self.policy) { 606 | (Some(priority), Some(policy)) => f(set_thread_priority_and_policy( 607 | thread_native_id(), 608 | priority, 609 | policy, 610 | )), 611 | (Some(priority), None) => f(priority.set_for_current()), 612 | (None, Some(_policy)) => { 613 | unimplemented!("Setting the policy separately isn't currently supported."); 614 | } 615 | _ => f(Ok(())), 616 | } 617 | } 618 | 619 | #[cfg(windows)] 620 | fn spawn_wrapper(self, f: F) -> impl FnOnce() -> T 621 | where 622 | F: FnOnce(Result<(), Error>) -> T, 623 | F: Send, 624 | T: Send, 625 | { 626 | move || { 627 | let mut result = match (self.priority, self.winapi_priority) { 628 | (Some(priority), None) => set_thread_priority(thread_native_id(), priority), 629 | (_, Some(priority)) => set_winapi_thread_priority(thread_native_id(), priority), 630 | _ => Ok(()), 631 | }; 632 | if result.is_ok() && self.boost_enabled { 633 | result = set_current_thread_priority_boost(self.boost_enabled); 634 | } 635 | if result.is_ok() { 636 | if let Some(ideal_processor) = self.ideal_processor { 637 | result = set_current_thread_ideal_processor(ideal_processor).map(|_| ()); 638 | } 639 | } 640 | f(result) 641 | } 642 | } 643 | 644 | /// Spawns a new thread by taking ownership of the `Builder`, and returns an 645 | /// [`std::io::Result`] to its [`std::thread::JoinHandle`]. 646 | /// 647 | /// See [`std::thread::Builder::spawn`] 648 | pub fn spawn(mut self, f: F) -> std::io::Result> 649 | where 650 | F: FnOnce(Result<(), Error>) -> T, 651 | F: Send + 'static, 652 | T: Send + 'static, 653 | { 654 | self.build_std().spawn(self.spawn_wrapper(f)) 655 | } 656 | 657 | /// Spawns a new scoped thread by taking ownership of the `Builder`, and returns an 658 | /// [`std::io::Result`] to its [`std::thread::ScopedJoinHandle`]. 659 | /// 660 | /// See [`std::thread::Builder::spawn_scoped`] 661 | #[rustversion::since(1.63)] 662 | pub fn spawn_scoped<'scope, 'env, F, T>( 663 | mut self, 664 | scope: &'scope std::thread::Scope<'scope, 'env>, 665 | f: F, 666 | ) -> std::io::Result> 667 | where 668 | F: FnOnce(Result<(), Error>) -> T, 669 | F: Send + 'scope, 670 | T: Send + 'scope, 671 | { 672 | self.build_std().spawn_scoped(scope, self.spawn_wrapper(f)) 673 | } 674 | 675 | fn build_std(&mut self) -> std::thread::Builder { 676 | let mut builder = std::thread::Builder::new(); 677 | 678 | if let Some(name) = &self.name { 679 | builder = builder.name(name.to_owned()); 680 | } 681 | 682 | if let Some(stack_size) = self.stack_size { 683 | builder = builder.stack_size(stack_size); 684 | } 685 | 686 | builder 687 | } 688 | 689 | /// Spawns a new thread by taking ownership of the `Builder`, and returns an 690 | /// [`std::io::Result`] to its [`std::thread::JoinHandle`]. 691 | /// 692 | /// See [`std::thread::Builder::spawn`] 693 | pub fn spawn_careless(self, f: F) -> std::io::Result> 694 | where 695 | F: FnOnce() -> T, 696 | F: Send + 'static, 697 | T: Send + 'static, 698 | { 699 | self.spawn(careless_wrapper(f)) 700 | } 701 | 702 | /// Spawns a new scoped thread by taking ownership of the `Builder`, and returns an 703 | /// [`std::io::Result`] to its [`std::thread::ScopedJoinHandle`]. 704 | /// 705 | /// See [`std::thread::Builder::spawn_scoped`] 706 | #[rustversion::since(1.63)] 707 | pub fn spawn_scoped_careless<'scope, 'env, F, T>( 708 | self, 709 | scope: &'scope std::thread::Scope<'scope, 'env>, 710 | f: F, 711 | ) -> std::io::Result> 712 | where 713 | F: FnOnce() -> T, 714 | F: Send + 'scope, 715 | T: Send + 'scope, 716 | { 717 | self.spawn_scoped(scope, careless_wrapper(f)) 718 | } 719 | } 720 | 721 | /// Adds thread building functions using the priority. 722 | pub trait ThreadBuilderExt { 723 | /// Spawn a thread with set priority. The passed functor `f` is executed in the spawned thread and 724 | /// receives as the only argument the result of setting the thread priority. 725 | /// See [`std::thread::Builder::spawn`] and [`ThreadPriority::set_for_current`] for more info. 726 | /// 727 | /// # Example 728 | /// 729 | /// ```rust 730 | /// use thread_priority::*; 731 | /// use thread_priority::ThreadBuilderExt; 732 | /// 733 | /// let thread = std::thread::Builder::new() 734 | /// .name("MyNewThread".to_owned()) 735 | /// .spawn_with_priority(ThreadPriority::Max, |result| { 736 | /// // This is printed out from within the spawned thread. 737 | /// println!("Set priority result: {:?}", result); 738 | /// assert!(result.is_ok()); 739 | /// }).unwrap(); 740 | /// thread.join(); 741 | /// ``` 742 | fn spawn_with_priority( 743 | self, 744 | priority: ThreadPriority, 745 | f: F, 746 | ) -> std::io::Result> 747 | where 748 | F: FnOnce(Result<(), Error>) -> T, 749 | F: Send + 'static, 750 | T: Send + 'static; 751 | 752 | /// Spawn a scoped thread with set priority. The passed functor `f` is executed in the spawned thread and 753 | /// receives as the only argument the result of setting the thread priority. 754 | /// See [`std::thread::Builder::spawn_scoped`] and [`ThreadPriority::set_for_current`] for more info. 755 | /// 756 | /// # Example 757 | /// 758 | /// ```rust 759 | /// use thread_priority::*; 760 | /// use thread_priority::ThreadBuilderExt; 761 | /// 762 | /// let x = 0; 763 | /// 764 | /// std::thread::scope(|s|{ 765 | /// std::thread::Builder::new() 766 | /// .name("MyNewThread".to_owned()) 767 | /// .spawn_scoped_with_priority(s, ThreadPriority::Max, |result| { 768 | /// // This is printed out from within the spawned thread. 769 | /// println!("Set priority result: {:?}", result); 770 | /// assert!(result.is_ok()); 771 | /// dbg!(&x); 772 | /// }).unwrap(); 773 | /// }); 774 | /// ``` 775 | #[rustversion::since(1.63)] 776 | fn spawn_scoped_with_priority<'scope, 'env, F, T>( 777 | self, 778 | scope: &'scope std::thread::Scope<'scope, 'env>, 779 | priority: ThreadPriority, 780 | f: F, 781 | ) -> std::io::Result> 782 | where 783 | F: FnOnce(Result<(), Error>) -> T, 784 | F: Send + 'scope, 785 | T: Send + 'scope; 786 | } 787 | 788 | impl ThreadBuilderExt for std::thread::Builder { 789 | fn spawn_with_priority( 790 | self, 791 | priority: ThreadPriority, 792 | f: F, 793 | ) -> std::io::Result> 794 | where 795 | F: FnOnce(Result<(), Error>) -> T, 796 | F: Send + 'static, 797 | T: Send + 'static, 798 | { 799 | self.spawn(move || f(priority.set_for_current())) 800 | } 801 | 802 | #[rustversion::since(1.63)] 803 | fn spawn_scoped_with_priority<'scope, 'env, F, T>( 804 | self, 805 | scope: &'scope std::thread::Scope<'scope, 'env>, 806 | priority: ThreadPriority, 807 | f: F, 808 | ) -> std::io::Result> 809 | where 810 | F: FnOnce(Result<(), Error>) -> T, 811 | F: Send + 'scope, 812 | T: Send + 'scope, 813 | { 814 | self.spawn_scoped(scope, move || f(priority.set_for_current())) 815 | } 816 | } 817 | 818 | /// Adds scoped thread building functions using the priority. 819 | #[rustversion::since(1.63)] 820 | pub trait ThreadScopeExt<'scope> { 821 | /// Spawn a scoped thread with set priority. The passed functor `f` is executed in the spawned thread and 822 | /// receives as the only argument the result of setting the thread priority. 823 | /// See [`std::thread::Scope::spawn`] and [`ThreadPriority::set_for_current`] for more info. 824 | /// 825 | /// # Example 826 | /// 827 | /// ```rust 828 | /// use thread_priority::*; 829 | /// 830 | /// let x = 0; 831 | /// 832 | /// std::thread::scope(|s|{ 833 | /// s.spawn_with_priority(ThreadPriority::Max, |result| { 834 | /// // This is printed out from within the spawned thread. 835 | /// println!("Set priority result: {:?}", result); 836 | /// assert!(result.is_ok()); 837 | /// dbg!(&x); 838 | /// }); 839 | /// }); 840 | /// ``` 841 | fn spawn_with_priority( 842 | &'scope self, 843 | priority: ThreadPriority, 844 | f: F, 845 | ) -> std::thread::ScopedJoinHandle<'scope, T> 846 | where 847 | F: FnOnce(Result<(), Error>) -> T, 848 | F: Send + 'scope, 849 | T: Send + 'scope; 850 | } 851 | 852 | #[rustversion::since(1.63)] 853 | impl<'scope> ThreadScopeExt<'scope> for std::thread::Scope<'scope, '_> { 854 | fn spawn_with_priority( 855 | &'scope self, 856 | priority: ThreadPriority, 857 | f: F, 858 | ) -> std::thread::ScopedJoinHandle<'scope, T> 859 | where 860 | F: FnOnce(Result<(), Error>) -> T, 861 | F: Send + 'scope, 862 | T: Send + 'scope, 863 | { 864 | self.spawn(move || f(priority.set_for_current())) 865 | } 866 | } 867 | 868 | /// Spawns a thread with the specified priority. 869 | /// 870 | /// See [`ThreadBuilderExt::spawn_with_priority`]. 871 | /// 872 | /// ```rust 873 | /// use thread_priority::*; 874 | /// 875 | /// let thread = spawn(ThreadPriority::Max, |result| { 876 | /// // This is printed out from within the spawned thread. 877 | /// println!("Set priority result: {:?}", result); 878 | /// assert!(result.is_ok()); 879 | /// }); 880 | /// thread.join(); 881 | /// ``` 882 | pub fn spawn(priority: ThreadPriority, f: F) -> std::thread::JoinHandle 883 | where 884 | F: FnOnce(Result<(), Error>) -> T, 885 | F: Send + 'static, 886 | T: Send + 'static, 887 | { 888 | std::thread::spawn(move || f(priority.set_for_current())) 889 | } 890 | 891 | /// Spawns a scoped thread with the specified priority. 892 | /// 893 | /// See [`ThreadBuilderExt::spawn_with_priority`]. 894 | /// 895 | /// ```rust 896 | /// use thread_priority::*; 897 | /// 898 | /// let x = 0; 899 | /// 900 | /// std::thread::scope(|s| { 901 | /// spawn_scoped(s, ThreadPriority::Max, |result| { 902 | /// // This is printed out from within the spawned thread. 903 | /// println!("Set priority result: {:?}", result); 904 | /// assert!(result.is_ok()); 905 | /// dbg!(&x); 906 | /// }); 907 | /// }); 908 | /// ``` 909 | #[rustversion::since(1.63)] 910 | pub fn spawn_scoped<'scope, 'env, F, T>( 911 | scope: &'scope std::thread::Scope<'scope, 'env>, 912 | priority: ThreadPriority, 913 | f: F, 914 | ) -> std::io::Result> 915 | where 916 | F: FnOnce(Result<(), Error>) -> T, 917 | F: Send + 'scope, 918 | T: Send + 'scope, 919 | { 920 | Ok(scope.spawn(move || f(priority.set_for_current()))) 921 | } 922 | 923 | /// Spawns a thread with the specified priority. 924 | /// This is different from [`spawn`] in a way that the passed function doesn't 925 | /// need to accept the [`ThreadPriority::set_for_current`] result. 926 | /// In case of an error, the error is logged using the logging facilities. 927 | /// 928 | /// See [`spawn`]. 929 | /// 930 | /// ```rust 931 | /// use thread_priority::*; 932 | /// 933 | /// let thread = spawn_careless(ThreadPriority::Max, || { 934 | /// // This is printed out from within the spawned thread. 935 | /// println!("We don't care about the priority result."); 936 | /// }); 937 | /// thread.join(); 938 | /// ``` 939 | pub fn spawn_careless(priority: ThreadPriority, f: F) -> std::thread::JoinHandle 940 | where 941 | F: FnOnce() -> T, 942 | F: Send + 'static, 943 | T: Send + 'static, 944 | { 945 | std::thread::spawn(move || careless_wrapper(f)(priority.set_for_current())) 946 | } 947 | 948 | /// Spawns a scoped thread with the specified priority. 949 | /// This is different from [`spawn_scoped`] in a way that the passed function doesn't 950 | /// need to accept the [`ThreadPriority::set_for_current`] result. 951 | /// In case of an error, the error is logged using the logging facilities. 952 | /// 953 | /// See [`spawn_scoped`]. 954 | /// 955 | /// ```rust 956 | /// use thread_priority::*; 957 | /// 958 | /// let x = 0; 959 | /// 960 | /// std::thread::scope(|s| { 961 | /// spawn_scoped_careless(s, ThreadPriority::Max, || { 962 | /// // This is printed out from within the spawned thread. 963 | /// println!("We don't care about the priority result."); 964 | /// dbg!(&x); 965 | /// }); 966 | /// }); 967 | /// ``` 968 | #[rustversion::since(1.63)] 969 | pub fn spawn_scoped_careless<'scope, 'env, F, T>( 970 | scope: &'scope std::thread::Scope<'scope, 'env>, 971 | priority: ThreadPriority, 972 | f: F, 973 | ) -> std::io::Result> 974 | where 975 | F: FnOnce() -> T, 976 | F: Send + 'scope, 977 | T: Send + 'scope, 978 | { 979 | Ok(scope.spawn(move || careless_wrapper(f)(priority.set_for_current()))) 980 | } 981 | -------------------------------------------------------------------------------- /src/unix.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the unix thread control. 2 | //! 3 | //! The crate's prelude doesn't have much control over 4 | //! the unix threads, and this module provides 5 | //! better control over those. 6 | 7 | use std::convert::TryFrom; 8 | 9 | #[cfg(target_os = "android")] 10 | use libc::SCHED_NORMAL as SCHED_OTHER; 11 | #[cfg(not(target_os = "android"))] 12 | use libc::SCHED_OTHER; 13 | #[cfg(target_os = "vxworks")] 14 | use libc::SCHED_SPORADIC; 15 | #[cfg(any(target_os = "linux", target_os = "android"))] 16 | use libc::{SCHED_BATCH, SCHED_IDLE}; 17 | use libc::{SCHED_FIFO, SCHED_RR}; 18 | 19 | use crate::{Error, ThreadPriority, ThreadPriorityValue}; 20 | use std::mem::MaybeUninit; 21 | 22 | // Processes scheduled under one of the real-time policies 23 | // (SCHED_FIFO, SCHED_RR) have a sched_priority value in the range 1 24 | // (low) to 99 (high). 25 | // For threads scheduled under one of the normal scheduling policies 26 | // (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), sched_priority is not 27 | // used in scheduling decisions (it must be specified as 0). 28 | // 29 | 30 | /// An alias type for a thread id. 31 | pub type ThreadId = libc::pthread_t; 32 | 33 | /// The maximum value possible for niceness. Threads with this value 34 | /// of niceness have the highest priority possible 35 | pub const NICENESS_MAX: i8 = -20; 36 | /// The minimum value possible for niceness. Threads with this value 37 | /// of niceness have the lowest priority possible. 38 | pub const NICENESS_MIN: i8 = 19; 39 | 40 | /// Proxy structure to maintain compatibility between glibc and musl 41 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 42 | pub struct ScheduleParams { 43 | /// Copy of `sched_priority` from `libc::sched_param` 44 | pub sched_priority: libc::c_int, 45 | } 46 | 47 | fn errno() -> libc::c_int { 48 | unsafe { 49 | cfg_if::cfg_if! { 50 | if #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "android"))] { 51 | *libc::__errno() 52 | } else if #[cfg(target_os = "linux")] { 53 | *libc::__errno_location() 54 | } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] { 55 | *libc::__error() 56 | } else if #[cfg(target_os = "vxworks")] { 57 | libc::errnoGet() 58 | } else { 59 | compile_error!("Your OS is probably not supported.") 60 | } 61 | } 62 | } 63 | } 64 | 65 | fn set_errno(number: libc::c_int) { 66 | unsafe { 67 | cfg_if::cfg_if! { 68 | if #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "android"))] { 69 | *libc::__errno() = number; 70 | } else if #[cfg(target_os = "linux")] { 71 | *libc::__errno_location() = number; 72 | } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] { 73 | *libc::__error() = number; 74 | } else if #[cfg(target_os = "vxworks")] { 75 | let _ = libc::errnoSet(number); 76 | } else { 77 | compile_error!("Your OS is probably not supported.") 78 | } 79 | } 80 | } 81 | } 82 | 83 | fn do_with_errno libc::c_int>(f: F) -> Result { 84 | let return_value = f(); 85 | if return_value < 0 { 86 | Err(Error::OS(errno())) 87 | } else { 88 | Ok(return_value) 89 | } 90 | } 91 | 92 | /// A copy of the Linux kernel's sched_attr type. 93 | /// 94 | /// This structure can be used directly with the C api and is 95 | /// supposed to be fully-compatible. 96 | #[derive(Debug, Default)] 97 | #[cfg(any(target_os = "linux", target_os = "android"))] 98 | #[repr(C)] 99 | pub struct SchedAttr { 100 | size: u32, 101 | sched_policy: u32, 102 | sched_flags: u64, 103 | /// for SCHED_NORMAL and SCHED_BATCH 104 | sched_nice: i32, 105 | /// for SCHED_FIFO, SCHED_RR 106 | sched_priority: u32, 107 | /// for SCHED_DEADLINE 108 | sched_runtime: u64, 109 | /// for SCHED_DEADLINE 110 | sched_deadline: u64, 111 | /// for SCHED_DEADLINE 112 | sched_period: u64, 113 | /// Utilization hint 114 | sched_util_min: u32, 115 | /// Utilization hint 116 | sched_util_max: u32, 117 | } 118 | 119 | impl ScheduleParams { 120 | fn into_posix(self) -> libc::sched_param { 121 | let mut param = unsafe { MaybeUninit::::zeroed().assume_init() }; 122 | param.sched_priority = self.sched_priority; 123 | param 124 | } 125 | 126 | fn from_posix(sched_param: libc::sched_param) -> Self { 127 | ScheduleParams { 128 | sched_priority: sched_param.sched_priority, 129 | } 130 | } 131 | } 132 | 133 | #[cfg(any(target_os = "linux", target_os = "android"))] 134 | bitflags::bitflags! { 135 | /// Flags for controlling Deadline scheduling behavior. 136 | #[repr(transparent)] 137 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 138 | pub struct DeadlineFlags: u64 { 139 | /// Children created by [`libc::fork`] will not inherit privileged 140 | /// scheduling policies. 141 | const RESET_ON_FORK = 0x01; 142 | /// The thread may reclaim bandwidth that is unused by another 143 | /// realtime thread. 144 | const RECLAIM = 0x02; 145 | /// Allows a task to get informed about runtime overruns through the 146 | /// delivery of SIGXCPU signals. 147 | const DEADLINE_OVERRUN = 0x04; 148 | } 149 | } 150 | 151 | /// Returns scheduling attributes for the current thread. 152 | #[cfg(any(target_os = "linux", target_os = "android"))] 153 | pub fn get_thread_scheduling_attributes() -> Result { 154 | let mut sched_attr = SchedAttr::default(); 155 | let current_thread = 0; 156 | let flags = 0; 157 | let ret = unsafe { 158 | libc::syscall( 159 | libc::SYS_sched_getattr, 160 | current_thread, 161 | &mut sched_attr as *mut _, 162 | std::mem::size_of::() as u32, 163 | flags, 164 | ) 165 | }; 166 | if ret < 0 { 167 | return Err(Error::OS(errno())); 168 | } 169 | Ok(sched_attr) 170 | } 171 | 172 | /// The following "real-time" policies are also supported, for special time-critical applications 173 | /// that need precise control over the way in which runnable processes are selected for execution 174 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 175 | pub enum RealtimeThreadSchedulePolicy { 176 | /// A first-in, first-out policy 177 | Fifo, 178 | /// A round-robin policy 179 | RoundRobin, 180 | // Policy similar to Fifo 181 | /// A sporadic scheduling policy specific to VxWorks. 182 | #[cfg(target_os = "vxworks")] 183 | Sporadic, 184 | /// A deadline policy. Note, due to Linux expecting a pid_t and not a pthread_t, the given 185 | /// [ThreadId](struct.ThreadId) will be interpreted as a pid_t. This policy is NOT 186 | /// POSIX-compatible, so we only include it for linux targets. 187 | #[cfg(all( 188 | any(target_os = "linux", target_os = "android"), 189 | not(target_arch = "wasm32") 190 | ))] 191 | Deadline, 192 | } 193 | 194 | impl RealtimeThreadSchedulePolicy { 195 | fn to_posix(self) -> libc::c_int { 196 | match self { 197 | RealtimeThreadSchedulePolicy::Fifo => SCHED_FIFO, 198 | RealtimeThreadSchedulePolicy::RoundRobin => SCHED_RR, 199 | #[cfg(target_os = "vxworks")] 200 | RealtimeThreadSchedulePolicy::Sporadic => SCHED_SPORADIC, 201 | #[cfg(all( 202 | any(target_os = "linux", target_os = "android"), 203 | not(target_arch = "wasm32") 204 | ))] 205 | RealtimeThreadSchedulePolicy::Deadline => 6, 206 | } 207 | } 208 | } 209 | 210 | /// Normal (non-realtime) schedule policies 211 | /// For these schedule policies, [`niceness`](https://man7.org/linux/man-pages/man7/sched.7.html) 212 | /// is used. 213 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 214 | pub enum NormalThreadSchedulePolicy { 215 | /// For running very low priority background jobs. 216 | /// (Since Linux 2.6.23.) `SCHED_IDLE` can be used only at static priority 0; 217 | /// the process nice value has no influence for this policy. 218 | /// 219 | /// This policy is intended for running jobs at extremely low priority (lower even 220 | /// than a +19 nice value with the SCHED_OTHER or SCHED_BATCH policies). 221 | #[cfg(any(target_os = "linux", target_os = "android"))] 222 | Idle, 223 | /// For "batch" style execution of processes. 224 | /// (Since Linux 2.6.16.) `SCHED_BATCH` can be used only at static priority 0. 225 | /// This policy is similar to SCHED_OTHER in that it schedules the thread 226 | /// according to its dynamic priority (based on the nice value). The difference is 227 | /// that this policy will cause the scheduler to always assume that the thread is 228 | /// CPU-intensive. Consequently, the scheduler will apply a small scheduling penalty 229 | /// with respect to wakeup behavior, so that this thread is mildly disfavored in scheduling decisions. 230 | /// 231 | /// This policy is useful for workloads that are noninteractive, but do not want to lower their 232 | /// nice value, and for workloads that want a deterministic scheduling policy without interactivity 233 | /// causing extra preemptions (between the workload's tasks). 234 | #[cfg(any(target_os = "linux", target_os = "android"))] 235 | Batch, 236 | /// The standard round-robin time-sharing policy, also sometimes referred to as "Normal". 237 | /// 238 | /// `SCHED_OTHER` can be used at only static priority 0 (i.e., threads under real-time policies 239 | /// always have priority over `SCHED_OTHER` processes). `SCHED_OTHER` is the standard Linux 240 | /// time-sharing scheduler that is intended for all threads that do not require the special 241 | /// real-time mechanisms. 242 | /// 243 | /// The thread to run is chosen from the static priority 0 list based on a dynamic priority that 244 | /// is determined only inside this list. The dynamic priority is based on the nice value (see below) 245 | /// and is increased for each time quantum the thread is ready to run, but denied to run by the scheduler. 246 | /// 247 | /// This ensures fair progress among all `SCHED_OTHER` threads. 248 | /// 249 | /// In the Linux kernel source code, the `SCHED_OTHER` policy is actually named `SCHED_NORMAL`. 250 | Other, 251 | } 252 | impl NormalThreadSchedulePolicy { 253 | fn to_posix(self) -> libc::c_int { 254 | match self { 255 | #[cfg(any(target_os = "linux", target_os = "android"))] 256 | NormalThreadSchedulePolicy::Idle => SCHED_IDLE, 257 | #[cfg(any(target_os = "linux", target_os = "android"))] 258 | NormalThreadSchedulePolicy::Batch => SCHED_BATCH, 259 | NormalThreadSchedulePolicy::Other => SCHED_OTHER, 260 | } 261 | } 262 | } 263 | 264 | /// Thread schedule policy definition. 265 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 266 | pub enum ThreadSchedulePolicy { 267 | /// Normal thread schedule policies. 268 | Normal(NormalThreadSchedulePolicy), 269 | /// Realtime thread schedule policies. 270 | Realtime(RealtimeThreadSchedulePolicy), 271 | } 272 | impl ThreadSchedulePolicy { 273 | fn to_posix(self) -> libc::c_int { 274 | match self { 275 | ThreadSchedulePolicy::Normal(p) => p.to_posix(), 276 | ThreadSchedulePolicy::Realtime(p) => p.to_posix(), 277 | } 278 | } 279 | 280 | fn from_posix(policy: libc::c_int) -> Result { 281 | match policy { 282 | SCHED_OTHER => Ok(ThreadSchedulePolicy::Normal( 283 | NormalThreadSchedulePolicy::Other, 284 | )), 285 | #[cfg(any(target_os = "linux", target_os = "android"))] 286 | SCHED_BATCH => Ok(ThreadSchedulePolicy::Normal( 287 | NormalThreadSchedulePolicy::Batch, 288 | )), 289 | #[cfg(any(target_os = "linux", target_os = "android"))] 290 | SCHED_IDLE => Ok(ThreadSchedulePolicy::Normal( 291 | NormalThreadSchedulePolicy::Idle, 292 | )), 293 | SCHED_FIFO => Ok(ThreadSchedulePolicy::Realtime( 294 | RealtimeThreadSchedulePolicy::Fifo, 295 | )), 296 | SCHED_RR => Ok(ThreadSchedulePolicy::Realtime( 297 | RealtimeThreadSchedulePolicy::RoundRobin, 298 | )), 299 | #[cfg(target_os = "vxworks")] 300 | SCHED_SPORADIC => Ok(ThreadSchedulePolicy::Realtime( 301 | RealtimeThreadSchedulePolicy::Sporadic, 302 | )), 303 | #[cfg(all( 304 | any(target_os = "linux", target_os = "android"), 305 | not(target_arch = "wasm32") 306 | ))] 307 | 6 => Ok(ThreadSchedulePolicy::Realtime( 308 | RealtimeThreadSchedulePolicy::Deadline, 309 | )), 310 | _ => Err(Error::Ffi("Can't parse schedule policy from posix")), 311 | } 312 | } 313 | } 314 | 315 | /// Defines the type of the priority edge value: minimum or maximum. 316 | #[derive(Debug, Copy, Clone)] 317 | pub enum PriorityPolicyEdgeValueType { 318 | /// Specifies the minimum priority value for a policy. 319 | Minimum, 320 | /// Specifies the maximum priority value for a policy. 321 | Maximum, 322 | } 323 | 324 | impl ThreadPriority { 325 | /// Returns the maximum allowed value for using with the provided policy. 326 | /// The returned number is in the range of allowed values. 327 | pub fn max_value_for_policy(policy: ThreadSchedulePolicy) -> Result { 328 | Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Maximum) 329 | } 330 | 331 | /// Returns the minimum allowed value for using with the provided policy. 332 | /// The returned number is in the range of allowed values. 333 | pub fn min_value_for_policy(policy: ThreadSchedulePolicy) -> Result { 334 | Self::get_edge_value_for_policy(policy, PriorityPolicyEdgeValueType::Minimum) 335 | } 336 | 337 | /// Returns the edge priority for the provided policy. 338 | fn get_edge_value_for_policy( 339 | policy: ThreadSchedulePolicy, 340 | edge: PriorityPolicyEdgeValueType, 341 | ) -> Result { 342 | let get_edge_priority = match edge { 343 | PriorityPolicyEdgeValueType::Minimum => Self::get_min_priority, 344 | PriorityPolicyEdgeValueType::Maximum => Self::get_max_priority, 345 | }; 346 | 347 | match policy { 348 | #[cfg_attr( 349 | not(any(target_os = "linux", target_os = "android")), 350 | allow(unused_variables) 351 | )] 352 | ThreadSchedulePolicy::Normal(normal) => { 353 | cfg_if::cfg_if! { 354 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 355 | if normal == NormalThreadSchedulePolicy::Idle { 356 | // Only `0` can be returned for `Idle` threads on Linux/Android. 357 | Ok(0) 358 | } else { 359 | // Niceness can be used on Linux/Android. 360 | Ok(match edge { 361 | PriorityPolicyEdgeValueType::Minimum => NICENESS_MIN as libc::c_int, 362 | PriorityPolicyEdgeValueType::Maximum => NICENESS_MAX as libc::c_int, 363 | }) 364 | } 365 | } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "vxworks"))] { 366 | // macOS/iOS and VxWorks allow specifying the priority using sched params. 367 | get_edge_priority(policy) 368 | } else { 369 | Err(Error::Priority( 370 | "Unsupported thread priority for this OS. Change the scheduling policy or use a supported OS.", 371 | )) 372 | } 373 | } 374 | } 375 | _ => get_edge_priority(policy), 376 | } 377 | } 378 | 379 | /// Returns the maximum scheduling priority for the POSIX policy. 380 | fn get_max_priority(policy: ThreadSchedulePolicy) -> Result { 381 | do_with_errno(|| unsafe { libc::sched_get_priority_max(policy.to_posix()) }) 382 | } 383 | 384 | /// Returns the minimum scheduling priority for the POSIX policy. 385 | fn get_min_priority(policy: ThreadSchedulePolicy) -> Result { 386 | do_with_errno(|| unsafe { libc::sched_get_priority_min(policy.to_posix()) }) 387 | } 388 | 389 | /// Checks that the passed priority value is within the range of allowed values for using with the provided policy. 390 | pub fn to_allowed_value_for_policy( 391 | priority: libc::c_int, 392 | policy: ThreadSchedulePolicy, 393 | ) -> Result { 394 | let min_priority = Self::min_value_for_policy(policy)?; 395 | let max_priority = Self::max_value_for_policy(policy)?; 396 | let (min, max) = ( 397 | std::cmp::min(min_priority, max_priority), 398 | std::cmp::max(min_priority, max_priority), 399 | ); 400 | let allowed_range = min..=max; 401 | if allowed_range.contains(&priority) { 402 | Ok(priority) 403 | } else { 404 | Err(Error::PriorityNotInRange(allowed_range)) 405 | } 406 | } 407 | 408 | /// Converts the priority stored to a posix number. 409 | /// POSIX value can not be known without knowing the scheduling policy 410 | /// 411 | /// 412 | /// For threads scheduled under one of the normal scheduling policies (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), sched_priority is not used in scheduling decisions (it must be specified as 0). 413 | /// Source: 414 | /// Due to this restriction of normal scheduling policies and the intention of the library, the niceness is used 415 | /// instead for such processes. 416 | pub fn to_posix(self, policy: ThreadSchedulePolicy) -> Result { 417 | let ret = match self { 418 | ThreadPriority::Min => match policy { 419 | // SCHED_DEADLINE doesn't really have a notion of priority, this is an error 420 | #[cfg(all( 421 | any(target_os = "linux", target_os = "android"), 422 | not(target_arch = "wasm32") 423 | ))] 424 | ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err( 425 | Error::Priority("Deadline scheduling must use deadline priority."), 426 | ), 427 | _ => Self::min_value_for_policy(policy).map(|v| v as u32), 428 | }, 429 | ThreadPriority::Crossplatform(ThreadPriorityValue(p)) => match policy { 430 | // SCHED_DEADLINE doesn't really have a notion of priority, this is an error 431 | #[cfg(all( 432 | any(target_os = "linux", target_os = "android"), 433 | not(target_arch = "wasm32") 434 | ))] 435 | ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err( 436 | Error::Priority("Deadline scheduling must use deadline priority."), 437 | ), 438 | ThreadSchedulePolicy::Realtime(_) => { 439 | Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32) 440 | } 441 | // XNU and the derivatives allow to change the priority 442 | // for the SCHED_OTHER policy. 443 | // 444 | #[cfg(all( 445 | any(target_os = "macos", target_os = "ios", target_os = "vxworks"), 446 | not(target_arch = "wasm32") 447 | ))] 448 | ThreadSchedulePolicy::Normal(_) => { 449 | Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32) 450 | } 451 | #[cfg(not(all( 452 | any(target_os = "macos", target_os = "ios", target_os = "vxworks"), 453 | not(target_arch = "wasm32") 454 | )))] 455 | ThreadSchedulePolicy::Normal(_) => { 456 | // Mapping a [0..100] priority into niceness [-20..20] needs reversing the ratio, 457 | // as the lowest nice is actually the highest priority. 458 | let niceness_values = NICENESS_MAX.abs() + NICENESS_MIN.abs(); 459 | let ratio = 1f32 - (p as f32 / ThreadPriorityValue::MAX.0 as f32); 460 | let niceness = ((niceness_values as f32 * ratio) as i8 + NICENESS_MAX) as i32; 461 | Self::to_allowed_value_for_policy(niceness, policy).map(|v| v as u32) 462 | } 463 | }, 464 | // TODO avoid code duplication. 465 | ThreadPriority::Os(crate::ThreadPriorityOsValue(p)) => match policy { 466 | // SCHED_DEADLINE doesn't really have a notion of priority, this is an error 467 | #[cfg(all( 468 | any(target_os = "linux", target_os = "android"), 469 | not(target_arch = "wasm32") 470 | ))] 471 | ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err( 472 | Error::Priority("Deadline scheduling must use deadline priority."), 473 | ), 474 | _ => Self::to_allowed_value_for_policy(p as i32, policy).map(|v| v as u32), 475 | }, 476 | ThreadPriority::Max => match policy { 477 | // SCHED_DEADLINE doesn't really have a notion of priority, this is an error 478 | #[cfg(all( 479 | any(target_os = "linux", target_os = "android"), 480 | not(target_arch = "wasm32") 481 | ))] 482 | ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => Err( 483 | Error::Priority("Deadline scheduling must use deadline priority."), 484 | ), 485 | _ => Self::max_value_for_policy(policy).map(|v| v as u32), 486 | }, 487 | #[cfg(all( 488 | any(target_os = "linux", target_os = "android"), 489 | not(target_arch = "wasm32") 490 | ))] 491 | ThreadPriority::Deadline { .. } => Err(Error::Priority( 492 | "Deadline is non-POSIX and cannot be converted.", 493 | )), 494 | }; 495 | ret.map(|p| p as libc::c_int) 496 | } 497 | 498 | /// Gets priority value from POSIX value. 499 | /// In order to interpret it correctly, you should also take scheduling policy 500 | /// into account. 501 | pub fn from_posix(params: ScheduleParams) -> ThreadPriority { 502 | ThreadPriority::Crossplatform(ThreadPriorityValue(params.sched_priority as u8)) 503 | } 504 | } 505 | 506 | #[cfg(any(target_os = "linux", target_os = "android"))] 507 | fn set_thread_priority_and_policy_deadline( 508 | native: ThreadId, 509 | priority: ThreadPriority, 510 | ) -> Result<(), Error> { 511 | use std::convert::TryInto as _; 512 | 513 | let (runtime, deadline, period, flags) = match priority { 514 | ThreadPriority::Deadline { 515 | runtime, 516 | deadline, 517 | period, 518 | flags, 519 | } => (|| { 520 | Ok(( 521 | runtime.as_nanos().try_into()?, 522 | deadline.as_nanos().try_into()?, 523 | period.as_nanos().try_into()?, 524 | flags, 525 | )) 526 | })() 527 | .map_err(|_: std::num::TryFromIntError| { 528 | Error::Priority("Deadline policy durations don't fit into a `u64`.") 529 | })?, 530 | _ => { 531 | return Err(Error::Priority( 532 | "Deadline policy given without deadline priority.", 533 | )); 534 | } 535 | }; 536 | let tid = native as libc::pid_t; 537 | let sched_attr = SchedAttr { 538 | size: std::mem::size_of::() as u32, 539 | sched_policy: RealtimeThreadSchedulePolicy::Deadline.to_posix() as u32, 540 | sched_runtime: runtime, 541 | sched_deadline: deadline, 542 | sched_period: period, 543 | sched_flags: flags.bits(), 544 | ..Default::default() 545 | }; 546 | let ret = 547 | unsafe { libc::syscall(libc::SYS_sched_setattr, tid, &sched_attr as *const _, 0) as i32 }; 548 | 549 | match ret { 550 | 0 => Ok(()), 551 | e => Err(Error::OS(e)), 552 | } 553 | } 554 | 555 | /// Sets thread's priority and schedule policy 556 | /// 557 | /// * May require privileges 558 | /// 559 | /// # Usage 560 | /// 561 | /// Setting thread priority to minimum with normal schedule policy: 562 | /// 563 | /// ```rust 564 | /// use thread_priority::*; 565 | /// 566 | /// let thread_id = thread_native_id(); 567 | /// assert!(set_thread_priority_and_policy(thread_id, 568 | /// ThreadPriority::Min, 569 | /// ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo)).is_ok()); 570 | /// ``` 571 | /// 572 | /// # Note 573 | /// 574 | /// In case the value is specified as [`ThreadPriority::Crossplatform`] and is incompatible with the policy, an error is returned. 575 | /// However if [`ThreadPriority::Min`] or [`ThreadPriority::Max`] are used, the correct value is used automatically according 576 | /// to the range of the policy's allowed values. 577 | pub fn set_thread_priority_and_policy( 578 | native: ThreadId, 579 | priority: ThreadPriority, 580 | policy: ThreadSchedulePolicy, 581 | ) -> Result<(), Error> { 582 | match policy { 583 | // SCHED_DEADLINE policy requires its own syscall 584 | #[cfg(any(target_os = "linux", target_os = "android"))] 585 | ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) => { 586 | set_thread_priority_and_policy_deadline(native, priority) 587 | } 588 | _ => { 589 | let fixed_priority = priority.to_posix(policy)?; 590 | // On VxWorks, macOS and iOS it is possible to set the priority 591 | // this way. 592 | if matches!(policy, ThreadSchedulePolicy::Realtime(_)) 593 | || cfg!(any( 594 | target_os = "macos", 595 | target_os = "ios", 596 | target_os = "vxworks" 597 | )) 598 | { 599 | // If the policy is a realtime one, the priority is set via 600 | // pthread_setschedparam. 601 | let params = ScheduleParams { 602 | sched_priority: fixed_priority, 603 | } 604 | .into_posix(); 605 | 606 | let ret = unsafe { 607 | libc::pthread_setschedparam( 608 | native, 609 | policy.to_posix(), 610 | ¶ms as *const libc::sched_param, 611 | ) 612 | }; 613 | 614 | match ret { 615 | 0 => Ok(()), 616 | e => Err(Error::OS(e)), 617 | } 618 | } else { 619 | //VxWorks does not have set priority function 620 | #[cfg(target_os = "vxworks")] 621 | unsafe fn setpriority( 622 | _which: u32, 623 | _who: u32, 624 | _priority: libc::c_int, 625 | ) -> libc::c_int { 626 | set_errno(libc::ENOSYS); 627 | -1 628 | } 629 | 630 | #[cfg(not(target_os = "vxworks"))] 631 | use libc::setpriority; 632 | 633 | // Normal priority threads must be set with static priority 0. 634 | let params = ScheduleParams { sched_priority: 0 }.into_posix(); 635 | 636 | let ret = unsafe { 637 | libc::pthread_setschedparam( 638 | native, 639 | policy.to_posix(), 640 | ¶ms as *const libc::sched_param, 641 | ) 642 | }; 643 | 644 | if ret != 0 { 645 | return Err(Error::OS(ret)); 646 | } 647 | 648 | // Normal priority threads adjust relative priority through niceness. 649 | set_errno(0); 650 | let ret = unsafe { setpriority(libc::PRIO_PROCESS, 0, fixed_priority) }; 651 | if ret != 0 { 652 | return Err(Error::OS(errno())); 653 | } 654 | 655 | Ok(()) 656 | } 657 | } 658 | } 659 | } 660 | 661 | /// Set current thread's priority. 662 | /// In order to properly map a value of the thread priority, the thread scheduling 663 | /// must be known. This function attempts to retrieve the current thread's 664 | /// scheduling policy and thus map the priority value correctly, so that it fits 665 | /// within the scheduling policy's allowed range of values. 666 | /// 667 | /// * May require privileges 668 | /// 669 | /// ```rust 670 | /// use thread_priority::*; 671 | /// 672 | /// let thread_id = thread_native_id(); 673 | /// assert!(set_current_thread_priority(ThreadPriority::Min).is_ok()); 674 | /// ``` 675 | pub fn set_current_thread_priority(priority: ThreadPriority) -> Result<(), Error> { 676 | let thread_id = thread_native_id(); 677 | let policy = thread_schedule_policy()?; 678 | set_thread_priority_and_policy(thread_id, priority, policy) 679 | } 680 | 681 | /// Returns policy parameters (schedule policy and other schedule parameters) for current process 682 | /// 683 | /// # Usage 684 | /// 685 | /// ```rust 686 | /// use thread_priority::*; 687 | /// 688 | /// assert!(thread_schedule_policy().is_ok()); 689 | /// ``` 690 | pub fn thread_schedule_policy() -> Result { 691 | thread_schedule_policy_param(thread_native_id()).map(|policy| policy.0) 692 | } 693 | 694 | /// Returns policy parameters (schedule policy and other schedule parameters) 695 | /// 696 | /// # Usage 697 | /// 698 | /// ```rust 699 | /// use thread_priority::*; 700 | /// 701 | /// let thread_id = thread_native_id(); 702 | /// assert!(thread_schedule_policy_param(thread_id).is_ok()); 703 | /// ``` 704 | pub fn thread_schedule_policy_param( 705 | native: ThreadId, 706 | ) -> Result<(ThreadSchedulePolicy, ScheduleParams), Error> { 707 | unsafe { 708 | let mut policy = 0i32; 709 | let mut params = ScheduleParams { sched_priority: 0 }.into_posix(); 710 | 711 | let ret = libc::pthread_getschedparam( 712 | native, 713 | &mut policy as *mut libc::c_int, 714 | &mut params as *mut libc::sched_param, 715 | ); 716 | match ret { 717 | 0 => Ok(( 718 | ThreadSchedulePolicy::from_posix(policy)?, 719 | ScheduleParams::from_posix(params), 720 | )), 721 | e => Err(Error::OS(e)), 722 | } 723 | } 724 | } 725 | 726 | /// Get the thread's priority value. 727 | pub fn get_thread_priority(native: ThreadId) -> Result { 728 | Ok(ThreadPriority::from_posix( 729 | thread_schedule_policy_param(native)?.1, 730 | )) 731 | } 732 | 733 | /// Get current thread's priority value. 734 | pub fn get_current_thread_priority() -> Result { 735 | get_thread_priority(thread_native_id()) 736 | } 737 | 738 | /// A helper trait for other threads to implement to be able to call methods 739 | /// on threads themselves. 740 | /// 741 | /// ```rust 742 | /// use thread_priority::*; 743 | /// 744 | /// assert!(std::thread::current().get_priority().is_ok()); 745 | /// 746 | /// let join_handle = std::thread::spawn(|| println!("Hello world!")); 747 | /// assert!(join_handle.thread().get_priority().is_ok()); 748 | /// 749 | /// join_handle.join(); 750 | /// ``` 751 | pub trait ThreadExt { 752 | /// Gets the current thread's priority. 753 | /// For more info read [`get_current_thread_priority`]. 754 | /// 755 | /// ```rust 756 | /// use thread_priority::*; 757 | /// 758 | /// assert!(std::thread::current().get_priority().is_ok()); 759 | /// ``` 760 | fn get_priority(&self) -> Result { 761 | get_current_thread_priority() 762 | } 763 | 764 | /// Sets the current thread's priority. 765 | /// For more info see [`ThreadPriority::set_for_current`]. 766 | /// 767 | /// ```rust 768 | /// use thread_priority::*; 769 | /// 770 | /// assert!(std::thread::current().set_priority(ThreadPriority::Min).is_ok()); 771 | /// ``` 772 | fn set_priority(&self, priority: ThreadPriority) -> Result<(), Error> { 773 | priority.set_for_current() 774 | } 775 | 776 | /// Gets the current thread's schedule policy. 777 | /// For more info read [`thread_schedule_policy`]. 778 | fn get_schedule_policy(&self) -> Result { 779 | thread_schedule_policy() 780 | } 781 | 782 | /// Returns current thread's schedule policy and parameters. 783 | /// For more info read [`thread_schedule_policy_param`]. 784 | fn get_schedule_policy_param(&self) -> Result<(ThreadSchedulePolicy, ScheduleParams), Error> { 785 | thread_schedule_policy_param(thread_native_id()) 786 | } 787 | 788 | /// Sets current thread's schedule policy. 789 | /// For more info read [`set_thread_priority_and_policy`]. 790 | fn set_priority_and_policy( 791 | &self, 792 | policy: ThreadSchedulePolicy, 793 | priority: ThreadPriority, 794 | ) -> Result<(), Error> { 795 | cfg_if::cfg_if! { 796 | if #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_arch = "wasm32")))] { 797 | if policy == ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) { 798 | set_thread_priority_and_policy(thread_native_id(), ThreadPriority::Crossplatform(ThreadPriorityValue(0)), policy) 799 | } else { 800 | set_thread_priority_and_policy(thread_native_id(), priority, policy) 801 | } 802 | } else { 803 | set_thread_priority_and_policy(thread_native_id(), priority, policy) 804 | } 805 | } 806 | } 807 | 808 | /// Returns native unix thread id. 809 | /// For more info read [`thread_native_id`]. 810 | /// 811 | /// ```rust 812 | /// use thread_priority::*; 813 | /// 814 | /// assert!(std::thread::current().get_native_id().unwrap() > 0); 815 | fn get_native_id(&self) -> Result; 816 | } 817 | 818 | /// Auto-implementation of this trait for the [`std::thread::Thread`]. 819 | impl ThreadExt for std::thread::Thread { 820 | fn get_native_id(&self) -> Result { 821 | if self.id() == std::thread::current().id() { 822 | Ok(thread_native_id()) 823 | } else { 824 | Err(Error::Priority( 825 | "The `ThreadExt::get_native_id()` is currently limited to be called on the current thread.", 826 | )) 827 | } 828 | } 829 | } 830 | 831 | /// Returns current thread id, which is the current OS's native handle. 832 | /// It may or may not be equal or even related to rust's thread id, 833 | /// there is absolutely no guarantee for that. 834 | /// 835 | /// # Usage 836 | /// 837 | /// ```rust 838 | /// use thread_priority::thread_native_id; 839 | /// 840 | /// assert!(thread_native_id() > 0); 841 | /// ``` 842 | pub fn thread_native_id() -> ThreadId { 843 | unsafe { libc::pthread_self() } 844 | } 845 | 846 | impl TryFrom for ThreadPriority { 847 | type Error = &'static str; 848 | 849 | fn try_from(value: u8) -> Result { 850 | if let 0..=100 = value { 851 | Ok(ThreadPriority::Crossplatform(ThreadPriorityValue(value))) 852 | } else { 853 | Err("The thread priority value must be in range of [0; 100].") 854 | } 855 | } 856 | } 857 | 858 | #[cfg(test)] 859 | mod tests { 860 | use crate::unix::*; 861 | 862 | #[test] 863 | fn thread_schedule_policy_param_test() { 864 | let thread_id = thread_native_id(); 865 | 866 | assert!(thread_schedule_policy_param(thread_id).is_ok()); 867 | } 868 | 869 | // Running this test requires CAP_SYS_NICE. 870 | #[test] 871 | fn change_between_realtime_and_normal_policies_requires_capabilities() { 872 | use crate::ThreadPriorityOsValue; 873 | 874 | const TEST_PRIORITY: u8 = 15; 875 | 876 | let realtime_policy = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo); 877 | let normal_policy = ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other); 878 | 879 | // While we may desire an OS-specific priority, the reported value is always crossplatform. 880 | let desired_priority = ThreadPriority::Os(ThreadPriorityOsValue(TEST_PRIORITY as _)); 881 | let expected_priority = ThreadPriority::Crossplatform(ThreadPriorityValue(TEST_PRIORITY)); 882 | 883 | let thread = std::thread::current(); 884 | thread 885 | .set_priority_and_policy(realtime_policy, desired_priority) 886 | .expect("to set realtime fifo policy"); 887 | 888 | assert_eq!(thread.get_schedule_policy(), Ok(realtime_policy)); 889 | assert_eq!(thread.get_priority(), Ok(expected_priority)); 890 | 891 | thread 892 | .set_priority_and_policy(normal_policy, desired_priority) 893 | .expect("to set normal other policy"); 894 | 895 | assert_eq!(thread.get_schedule_policy(), Ok(normal_policy)); 896 | 897 | // On linux, normal priority threads always have static priority 0. Instead the "nice" value is used. 898 | #[cfg(not(target_os = "linux"))] 899 | assert_eq!(thread.get_priority(), Ok(expected_priority)); 900 | #[cfg(target_os = "linux")] 901 | { 902 | let nice = unsafe { libc::getpriority(0, 0) }; 903 | assert_eq!(nice, TEST_PRIORITY as i32); 904 | } 905 | } 906 | 907 | #[test] 908 | #[cfg(target_os = "linux")] 909 | fn set_deadline_policy() { 910 | // allow the identity operation for clarity 911 | #![allow(clippy::identity_op)] 912 | use std::time::Duration; 913 | 914 | assert!( 915 | set_thread_priority_and_policy( 916 | 0, // current thread 917 | ThreadPriority::Deadline { 918 | runtime: Duration::from_millis(1), 919 | deadline: Duration::from_millis(10), 920 | period: Duration::from_millis(100), 921 | flags: DeadlineFlags::RESET_ON_FORK, 922 | }, 923 | ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Deadline) 924 | ) 925 | .is_ok() 926 | ); 927 | 928 | let attributes = get_thread_scheduling_attributes().unwrap(); 929 | assert_eq!( 930 | attributes.sched_policy, 931 | RealtimeThreadSchedulePolicy::Deadline.to_posix() as u32 932 | ); 933 | assert_eq!(attributes.sched_runtime, 1 * 10_u64.pow(6)); 934 | assert_eq!(attributes.sched_deadline, 10 * 10_u64.pow(6)); 935 | assert_eq!(attributes.sched_period, 100 * 10_u64.pow(6)); 936 | assert_eq!(attributes.sched_flags, DeadlineFlags::RESET_ON_FORK.bits()); 937 | } 938 | } 939 | --------------------------------------------------------------------------------