#![warn(missing_docs)]
#![deny(warnings)]
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "android",
target_arch = "wasm32",
))]
pub mod unix;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::time::Duration;
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "android",
target_arch = "wasm32",
))]
pub use unix::*;
#[cfg(windows)]
pub mod windows;
#[cfg(windows)]
pub use windows::*;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Error {
Priority(&'static str),
PriorityNotInRange(std::ops::RangeInclusive<i32>),
OS(i32),
Ffi(&'static str),
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ThreadPriorityValue(u8);
impl ThreadPriorityValue {
pub const MAX: u8 = 99;
pub const MIN: u8 = 0;
}
impl std::convert::TryFrom<u8> for ThreadPriorityValue {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if (Self::MIN..=Self::MAX).contains(&value) {
Ok(Self(value))
} else {
Err("The value is not in the range of [0;99]")
}
}
}
#[allow(clippy::from_over_into)]
impl std::convert::Into<u8> for ThreadPriorityValue {
fn into(self) -> u8 {
self.0
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ThreadPriorityOsValue(u32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum ThreadPriority {
#[cfg_attr(
target_os = "windows",
doc = "\
The [`ThreadPriority::Min`] value is mapped to [`WinAPIThreadPriority::Lowest`] and not
[`WinAPIThreadPriority::Idle`] to avoid unexpected drawbacks. Use the specific value
to set it to [`WinAPIThreadPriority::Idle`] when it is really needed.
"
)]
Min,
Crossplatform(ThreadPriorityValue),
#[cfg_attr(
target_os = "windows",
doc = "\
The value is matched among possible values in Windows from [`WinAPIThreadPriority::Idle`] till
[`WinAPIThreadPriority::TimeCritical`]. This is due to windows only having from 7 to 9 possible
thread priorities and not `100` as it is allowed to have in the [`ThreadPriority::Crossplatform`]
variant.
"
)]
Os(ThreadPriorityOsValue),
#[cfg(any(target_os = "linux", target_os = "android"))]
Deadline {
runtime: Duration,
deadline: Duration,
period: Duration,
},
#[cfg_attr(
target_os = "windows",
doc = "\
The [`ThreadPriority::Max`] value is mapped to [`WinAPIThreadPriority::Highest`] and not
[`WinAPIThreadPriority::TimeCritical`] to avoid unexpected drawbacks. Use the specific value
to set it to [`WinAPIThreadPriority::TimeCritical`] when it is really needed.
"
)]
Max,
}
impl ThreadPriority {
pub fn set_for_current(self) -> Result<(), Error> {
set_current_thread_priority(self)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Thread {
pub priority: ThreadPriority,
pub id: ThreadId,
}
impl Thread {
pub fn current() -> Result<Thread, Error> {
Ok(Thread {
priority: get_current_thread_priority()?,
id: thread_native_id(),
})
}
}
#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct ThreadBuilder {
name: Option<String>,
stack_size: Option<usize>,
priority: Option<ThreadPriority>,
#[cfg(unix)]
policy: Option<ThreadSchedulePolicy>,
#[cfg(windows)]
winapi_priority: Option<WinAPIThreadPriority>,
#[cfg(windows)]
boost_enabled: bool,
#[cfg(windows)]
ideal_processor: Option<IdealProcessor>,
}
impl ThreadBuilder {
pub fn name<VALUE: Into<String>>(mut self, value: VALUE) -> Self {
self.name = Some(value.into());
self
}
pub fn stack_size<VALUE: Into<usize>>(mut self, value: VALUE) -> Self {
self.stack_size = Some(value.into());
self
}
pub fn priority<VALUE: Into<ThreadPriority>>(mut self, value: VALUE) -> Self {
self.priority = Some(value.into());
self
}
#[cfg(unix)]
pub fn policy<VALUE: Into<unix::ThreadSchedulePolicy>>(mut self, value: VALUE) -> Self {
self.policy = Some(value.into());
self
}
#[cfg(windows)]
pub fn winapi_priority<VALUE: Into<windows::WinAPIThreadPriority>>(
mut self,
value: VALUE,
) -> Self {
self.winapi_priority = Some(value.into());
self
}
#[cfg(windows)]
pub fn boost_enabled(mut self, value: bool) -> Self {
self.boost_enabled = value;
self
}
#[cfg(windows)]
pub fn ideal_processor<VALUE: Into<windows::IdealProcessor>>(mut self, value: VALUE) -> Self {
self.ideal_processor = Some(value.into());
self
}
#[cfg(unix)]
pub fn spawn<F, T>(mut self, f: F) -> std::io::Result<std::thread::JoinHandle<T>>
where
F: FnOnce(Result<(), Error>) -> T,
F: Send + 'static,
T: Send + 'static,
{
let priority = self.priority;
let policy = self.policy;
self.build_std().spawn(move || match (priority, policy) {
(Some(priority), Some(policy)) => f(set_thread_priority_and_policy(
thread_native_id(),
priority,
policy,
)),
(Some(priority), None) => f(priority.set_for_current()),
(None, Some(_policy)) => {
unimplemented!("Setting the policy separately isn't currently supported.");
}
_ => f(Ok(())),
})
}
#[cfg(windows)]
pub fn spawn<F, T>(mut self, f: F) -> std::io::Result<std::thread::JoinHandle<T>>
where
F: FnOnce(Result<(), Error>) -> T,
F: Send + 'static,
T: Send + 'static,
{
let thread_priority = self.priority;
let winapi_priority = self.winapi_priority;
let boost_enabled = self.boost_enabled;
let ideal_processor = self.ideal_processor;
self.build_std().spawn(move || {
let mut result = match (thread_priority, winapi_priority) {
(Some(priority), None) => set_thread_priority(thread_native_id(), priority),
(_, Some(priority)) => set_winapi_thread_priority(thread_native_id(), priority),
_ => Ok(()),
};
if result.is_ok() && boost_enabled {
result = set_current_thread_priority_boost(boost_enabled);
}
if result.is_ok() {
if let Some(ideal_processor) = ideal_processor {
result = set_current_thread_ideal_processor(ideal_processor).map(|_| ());
}
}
f(result)
})
}
fn build_std(&mut self) -> std::thread::Builder {
let mut builder = std::thread::Builder::new();
if let Some(name) = &self.name {
builder = builder.name(name.to_owned());
}
if let Some(stack_size) = self.stack_size {
builder = builder.stack_size(stack_size);
}
builder
}
pub fn spawn_careless<F, T>(self, f: F) -> std::io::Result<std::thread::JoinHandle<T>>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
self.spawn(|priority_set_result| {
if let Err(e) = priority_set_result {
log::warn!(
"Couldn't set the priority for the thread with Rust Thread ID {:?} named {:?}: {:?}",
std::thread::current().id(),
std::thread::current().name(),
e,
);
}
f()
})
}
}
pub trait ThreadBuilderExt {
fn spawn_with_priority<F, T>(
self,
priority: ThreadPriority,
f: F,
) -> std::io::Result<std::thread::JoinHandle<T>>
where
F: FnOnce(Result<(), Error>) -> T,
F: Send + 'static,
T: Send + 'static;
}
impl ThreadBuilderExt for std::thread::Builder {
fn spawn_with_priority<F, T>(
self,
priority: ThreadPriority,
f: F,
) -> std::io::Result<std::thread::JoinHandle<T>>
where
F: FnOnce(Result<(), Error>) -> T,
F: Send + 'static,
T: Send + 'static,
{
self.spawn(move || f(priority.set_for_current()))
}
}
pub fn spawn<F, T>(priority: ThreadPriority, f: F) -> std::thread::JoinHandle<T>
where
F: FnOnce(Result<(), Error>) -> T,
F: Send + 'static,
T: Send + 'static,
{
std::thread::spawn(move || f(priority.set_for_current()))
}
pub fn spawn_careless<F, T>(priority: ThreadPriority, f: F) -> std::thread::JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
std::thread::spawn(move || {
if let Err(e) = priority.set_for_current() {
log::warn!(
"Couldn't set the priority for the thread with Rust Thread ID {:?} named {:?}: {:?}",
std::thread::current().id(),
std::thread::current().name(),
e,
);
}
f()
})
}