thread_priority/
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
7use 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
21use crate::{Error, ThreadPriority};
22
23/// An alias type for specifying the ideal processor.
24/// Used in the WinAPI for affinity control.
25pub type IdealProcessor = u32;
26
27/// An alias type for a thread id.
28#[derive(Debug, Copy, Clone, Eq, PartialEq)]
29#[repr(transparent)]
30pub struct ThreadId(HANDLE);
31
32impl PartialOrd for ThreadId {
33    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
34        Some(self.cmp(other))
35    }
36}
37
38impl 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
44impl 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/// <https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority>
53#[repr(i32)]
54#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
55pub 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
96impl std::convert::TryFrom<ThreadPriority> for WinAPIThreadPriority {
97    type Error = crate::Error;
98
99    fn try_from(priority: ThreadPriority) -> Result<Self, Self::Error> {
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
135impl std::convert::TryFrom<THREAD_PRIORITY> for WinAPIThreadPriority {
136    type Error = crate::Error;
137
138    fn try_from(priority: THREAD_PRIORITY) -> Result<Self, Self::Error> {
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
154impl From<WinAPIThreadPriority> 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.
177pub 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.
198pub 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.
225pub 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/// ```
241pub fn get_thread_priority(native: ThreadId) -> Result<ThreadPriority, Error> {
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/// ```
265pub fn get_current_thread_priority() -> Result<ThreadPriority, Error> {
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/// ```
289pub 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/// ```
306pub 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.
316pub 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/// ```
342pub fn set_thread_ideal_processor(
343    native: ThreadId,
344    ideal_processor: IdealProcessor,
345) -> Result<IdealProcessor, Error> {
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.
360pub fn set_current_thread_ideal_processor(
361    ideal_processor: IdealProcessor,
362) -> Result<IdealProcessor, Error> {
363    set_thread_ideal_processor(thread_native_id(), ideal_processor)
364}
365
366impl std::convert::TryFrom<u32> for crate::ThreadPriorityOsValue {
367    type Error = ();
368
369    fn try_from(value: u32) -> Result<Self, Self::Error> {
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.
388pub 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<ThreadPriority, Error> {
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<ThreadId, Error>;
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<IdealProcessor, Error> {
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`].
452impl ThreadExt for std::thread::Thread {
453    fn get_native_id(&self) -> Result<ThreadId, Error> {
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}