Skip to main content

windows_thread_ambient_sys/
declared.rs

1// Copyright (c) Mike Grier.
2
3//! The declared aspects: WOW64 filesystem redirection, memory priority, and I/O
4//! priority.
5//!
6//! These are *declared*, not captured. A declared aspect has nothing to collect
7//! from the calling thread, so it is not part of any capture set: the caller
8//! states the value it wants installed, and leaving it unspecified means the
9//! running thread's own value is untouched.
10//!
11//! # Why each one is declared, and the reasons differ
12//!
13//! Stating this per aspect rather than as one blanket claim matters, because the
14//! reasons are not the same and only one of them is absolute.
15//!
16//! - **WOW64 filesystem redirection has no getter at all.**
17//!   `Wow64DisableWow64FsRedirection` yields an `OldValue` only as a side effect
18//!   of *disabling* redirection, and there is no way to observe the current
19//!   state without changing it. An aspect that cannot be read cannot be
20//!   transplanted; this is a mechanical impossibility, not a preference.
21//! - **Memory priority is readable**, through
22//!   `GetThreadInformation(ThreadMemoryPriority)`, so this one is a choice.
23//!   Priority is a policy about how work should compete for resources, not
24//!   something a caller implicitly consents to having remoted -- and silently
25//!   transplanting it is not the safe default either way, since a caller in a
26//!   background mode would have its remoted work quietly promoted or demoted
27//!   without saying so.
28//! - **I/O priority has no documented getter**, and does not move on its own: it
29//!   changes only in lockstep with CPU and memory priority, through background
30//!   mode. So there is no independent value to capture even in principle, and
31//!   declaring background mode declares all three together, which the type says
32//!   out loud rather than hiding.
33//!
34//! # Redirection only does anything in a 32-bit process
35//!
36//! `Wow64DisableWow64FsRedirection` is meaningful only for a 32-bit process on
37//! 64-bit Windows. In a 64-bit process there is no redirector to disable and the
38//! call fails. That failure is reported rather than swallowed, because a caller
39//! that asked for redirection to be disabled and silently did not get it would
40//! be reading a different filesystem than it believes.
41//!
42//! # Example
43//!
44//! ```
45//! use windows_thread_ambient_sys::Declared;
46//! use windows_thread_ambient_sys::declared::MemoryPriority;
47//!
48//! let entry = MemoryPriority::current()?;
49//!
50//! // Only what is named is installed; every other aspect is left alone.
51//! let declared = Declared::none().with_memory_priority(MemoryPriority::Low);
52//!
53//! let during = declared.with_applied(MemoryPriority::current)?;
54//! assert_eq!(during?, MemoryPriority::Low);
55//!
56//! // The thread is returned to whatever it had, not to an assumed default.
57//! assert_eq!(MemoryPriority::current()?, entry);
58//! # Ok::<(), Box<dyn std::error::Error>>(())
59//! ```
60
61use std::fmt;
62use std::io;
63
64use windows_sys::Win32::Storage::FileSystem::{
65    Wow64DisableWow64FsRedirection, Wow64RevertWow64FsRedirection,
66};
67use windows_sys::Win32::System::Threading::{
68    GetCurrentThread, GetThreadInformation, MEMORY_PRIORITY, MEMORY_PRIORITY_BELOW_NORMAL,
69    MEMORY_PRIORITY_INFORMATION, MEMORY_PRIORITY_LOW, MEMORY_PRIORITY_MEDIUM,
70    MEMORY_PRIORITY_NORMAL, MEMORY_PRIORITY_VERY_LOW, SetThreadInformation, SetThreadPriority,
71    THREAD_MODE_BACKGROUND_BEGIN, THREAD_MODE_BACKGROUND_END, ThreadMemoryPriority,
72};
73
74/// How a thread's memory pages compete for physical memory.
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
76#[non_exhaustive]
77pub enum MemoryPriority {
78    /// The lowest priority; pages are trimmed first.
79    VeryLow,
80    /// Below `BelowNormal`.
81    Low,
82    /// Between `Low` and `BelowNormal`.
83    Medium,
84    /// Below the default.
85    BelowNormal,
86    /// The default for a thread.
87    Normal,
88}
89
90impl MemoryPriority {
91    /// The Win32 value.
92    #[must_use]
93    pub const fn as_raw(self) -> MEMORY_PRIORITY {
94        match self {
95            Self::VeryLow => MEMORY_PRIORITY_VERY_LOW,
96            Self::Low => MEMORY_PRIORITY_LOW,
97            Self::Medium => MEMORY_PRIORITY_MEDIUM,
98            Self::BelowNormal => MEMORY_PRIORITY_BELOW_NORMAL,
99            Self::Normal => MEMORY_PRIORITY_NORMAL,
100        }
101    }
102
103    /// Interpret a Win32 value.
104    #[must_use]
105    pub const fn from_raw(raw: MEMORY_PRIORITY) -> Option<Self> {
106        match raw {
107            MEMORY_PRIORITY_VERY_LOW => Some(Self::VeryLow),
108            MEMORY_PRIORITY_LOW => Some(Self::Low),
109            MEMORY_PRIORITY_MEDIUM => Some(Self::Medium),
110            MEMORY_PRIORITY_BELOW_NORMAL => Some(Self::BelowNormal),
111            MEMORY_PRIORITY_NORMAL => Some(Self::Normal),
112            _ => None,
113        }
114    }
115
116    /// Read the calling thread's current memory priority.
117    ///
118    /// Provided because the value *is* readable, which is what makes this
119    /// aspect's declared status a deliberate choice rather than a limitation. A
120    /// consumer that decides transplanting is right for it can read the value
121    /// here and declare it; the crate simply does not do so on the caller's
122    /// behalf.
123    ///
124    /// # Errors
125    ///
126    /// Returns [`DeclaredError`] if the query fails or reports a value this
127    /// enumeration does not name.
128    pub fn current() -> Result<Self, DeclaredError> {
129        let mut info = MEMORY_PRIORITY_INFORMATION {
130            MemoryPriority: MEMORY_PRIORITY_NORMAL,
131        };
132        // SAFETY: the destination is a valid, correctly sized struct and the
133        // pseudo-handle needs no cleanup.
134        let ok = unsafe {
135            GetThreadInformation(
136                GetCurrentThread(),
137                ThreadMemoryPriority,
138                std::ptr::from_mut(&mut info).cast(),
139                size_of::<MEMORY_PRIORITY_INFORMATION>() as u32,
140            )
141        };
142        if ok == 0 {
143            return Err(DeclaredError::new(DeclaredAspect::MemoryPriority));
144        }
145        Self::from_raw(info.MemoryPriority)
146            .ok_or_else(|| DeclaredError::without_os_error(DeclaredAspect::MemoryPriority))
147    }
148
149    fn install(self) -> Result<(), DeclaredError> {
150        let info = MEMORY_PRIORITY_INFORMATION {
151            MemoryPriority: self.as_raw(),
152        };
153        // SAFETY: the source is a valid, correctly sized struct and the
154        // pseudo-handle needs no cleanup.
155        let ok = unsafe {
156            SetThreadInformation(
157                GetCurrentThread(),
158                ThreadMemoryPriority,
159                std::ptr::from_ref(&info).cast(),
160                size_of::<MEMORY_PRIORITY_INFORMATION>() as u32,
161            )
162        };
163        if ok == 0 {
164            return Err(DeclaredError::new(DeclaredAspect::MemoryPriority));
165        }
166        Ok(())
167    }
168}
169
170/// Whether the thread runs in background processing mode.
171///
172/// Background mode is not an I/O-priority knob on its own: entering it lowers
173/// CPU, I/O **and** memory priority together, and leaving it restores all three.
174/// The name says so rather than presenting it as an I/O setting that happens to
175/// have side effects.
176#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
177pub enum BackgroundMode {
178    /// Enter background processing mode for the operation.
179    Begin,
180    /// Leave background processing mode for the operation.
181    End,
182}
183
184impl BackgroundMode {
185    fn install(self) -> Result<(), DeclaredError> {
186        let value = match self {
187            Self::Begin => THREAD_MODE_BACKGROUND_BEGIN,
188            Self::End => THREAD_MODE_BACKGROUND_END,
189        };
190        // SAFETY: the pseudo-handle needs no cleanup and `value` is a documented
191        // background-mode selector.
192        let ok = unsafe { SetThreadPriority(GetCurrentThread(), value) };
193        if ok == 0 {
194            return Err(DeclaredError::new(DeclaredAspect::BackgroundMode));
195        }
196        Ok(())
197    }
198
199    /// The call that undoes this one.
200    const fn inverse(self) -> Self {
201        match self {
202            Self::Begin => Self::End,
203            Self::End => Self::Begin,
204        }
205    }
206}
207
208/// What to do with WOW64 filesystem redirection.
209///
210/// Only [`Disabled`](Self::Disabled) is expressible, because Windows offers no
211/// way to *enable* redirection that was never disabled, and no way to read the
212/// current state. The enum exists rather than a bare `bool` so that a later
213/// capability does not change the meaning of an existing value.
214#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
215#[non_exhaustive]
216pub enum Wow64Redirection {
217    /// Disable redirection for the operation, restoring it afterwards.
218    Disabled,
219}
220
221/// Which declared aspect an operation concerned.
222#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
223#[non_exhaustive]
224pub enum DeclaredAspect {
225    /// WOW64 filesystem redirection.
226    Wow64Redirection,
227    /// Thread memory priority.
228    MemoryPriority,
229    /// Background processing mode.
230    BackgroundMode,
231}
232
233/// A declared aspect could not be installed or restored.
234#[derive(Debug)]
235pub struct DeclaredError {
236    aspect: DeclaredAspect,
237    source: Option<io::Error>,
238}
239
240impl DeclaredError {
241    fn new(aspect: DeclaredAspect) -> Self {
242        Self {
243            aspect,
244            source: Some(io::Error::last_os_error()),
245        }
246    }
247
248    const fn without_os_error(aspect: DeclaredAspect) -> Self {
249        Self {
250            aspect,
251            source: None,
252        }
253    }
254
255    /// Which aspect failed.
256    #[must_use]
257    pub const fn aspect(&self) -> DeclaredAspect {
258        self.aspect
259    }
260
261    /// The underlying Win32 code, if there was one.
262    #[must_use]
263    pub fn raw_os_error(&self) -> Option<i32> {
264        self.source.as_ref().and_then(io::Error::raw_os_error)
265    }
266}
267
268impl fmt::Display for DeclaredError {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        let what = match self.aspect {
271            DeclaredAspect::Wow64Redirection => {
272                "WOW64 filesystem redirection could not be changed (it exists only \
273                 for a 32-bit process on 64-bit Windows)"
274            }
275            DeclaredAspect::MemoryPriority => "the thread memory priority could not be read or set",
276            DeclaredAspect::BackgroundMode => "background processing mode could not be changed",
277        };
278        match &self.source {
279            Some(source) => write!(f, "{what}: {source}"),
280            None => f.write_str(what),
281        }
282    }
283}
284
285impl std::error::Error for DeclaredError {
286    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
287        self.source
288            .as_ref()
289            .map(|source| source as &(dyn std::error::Error + 'static))
290    }
291}
292
293/// The declared aspects a caller wants installed.
294///
295/// Every field is optional, and `None` means **leave the running thread's own
296/// value alone**. That is not the same as declaring a default: a declared
297/// default would overwrite whatever the thread had.
298#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
299pub struct Declared {
300    /// WOW64 filesystem redirection.
301    pub wow64_redirection: Option<Wow64Redirection>,
302    /// Thread memory priority.
303    pub memory_priority: Option<MemoryPriority>,
304    /// Background processing mode, which moves CPU, I/O and memory priority
305    /// together.
306    pub background_mode: Option<BackgroundMode>,
307}
308
309impl Declared {
310    /// Declare nothing, leaving every aspect of the running thread alone.
311    #[must_use]
312    pub const fn none() -> Self {
313        Self {
314            wow64_redirection: None,
315            memory_priority: None,
316            background_mode: None,
317        }
318    }
319
320    /// Whether anything at all would be installed.
321    #[must_use]
322    pub const fn is_empty(&self) -> bool {
323        self.wow64_redirection.is_none()
324            && self.memory_priority.is_none()
325            && self.background_mode.is_none()
326    }
327
328    /// Declare WOW64 filesystem redirection.
329    #[must_use]
330    pub const fn with_wow64_redirection(mut self, value: Wow64Redirection) -> Self {
331        self.wow64_redirection = Some(value);
332        self
333    }
334
335    /// Declare a memory priority.
336    #[must_use]
337    pub const fn with_memory_priority(mut self, value: MemoryPriority) -> Self {
338        self.memory_priority = Some(value);
339        self
340    }
341
342    /// Declare a background processing mode.
343    #[must_use]
344    pub const fn with_background_mode(mut self, value: BackgroundMode) -> Self {
345        self.background_mode = Some(value);
346        self
347    }
348
349    /// Install these aspects on the calling thread until the guard is released.
350    ///
351    /// Aspects are installed in a fixed order -- background mode, then memory
352    /// priority, then WOW64 redirection -- and
353    /// [`release`](DeclaredGuard::release) undoes them in exact reverse, so the
354    /// thread passes back through each intermediate state rather than being
355    /// snapped to an assumed one.
356    ///
357    /// # Errors
358    ///
359    /// Returns [`DeclaredError`] if an aspect could not be installed. Every
360    /// aspect installed before the failing one is released first, so a failed
361    /// install leaves the thread as it found it.
362    pub fn install(&self) -> Result<DeclaredGuard, DeclaredError> {
363        let background = match self.background_mode {
364            Some(mode) => {
365                mode.install()?;
366                Some(mode)
367            }
368            None => None,
369        };
370
371        let memory = match self.memory_priority {
372            Some(priority) => {
373                let previous = MemoryPriority::current();
374                match previous.and_then(|previous| priority.install().map(|()| previous)) {
375                    Ok(previous) => Some(previous),
376                    Err(error) => {
377                        release_background(background);
378                        return Err(error);
379                    }
380                }
381            }
382            None => None,
383        };
384
385        let redirection = match self.wow64_redirection {
386            Some(Wow64Redirection::Disabled) => {
387                let mut old: *mut core::ffi::c_void = std::ptr::null_mut();
388                // SAFETY: `old` is a valid writable destination, and is passed
389                // back verbatim to the revert call in `release`.
390                let ok = unsafe { Wow64DisableWow64FsRedirection(&mut old) };
391                if ok == 0 {
392                    let error = DeclaredError::new(DeclaredAspect::Wow64Redirection);
393                    release_memory(memory);
394                    release_background(background);
395                    return Err(error);
396                }
397                Some(old)
398            }
399            None => None,
400        };
401
402        Ok(DeclaredGuard {
403            background,
404            memory,
405            redirection,
406            released: false,
407        })
408    }
409
410    /// Run `operation` with these aspects installed.
411    ///
412    /// A convenience over [`install`](Self::install) for callers that do not
413    /// need the operation's value when restoration fails. **A restore failure
414    /// discards the value**, because there is no room in this signature for
415    /// both; a caller that must keep the value even then should use `install`
416    /// and [`DeclaredGuard::release`] directly, as the composite does.
417    ///
418    /// # Errors
419    ///
420    /// Returns [`DeclaredError`] if an aspect could not be installed, in which
421    /// case `operation` did not run; or if an aspect could not be restored
422    /// afterwards, in which case it did run and its value is discarded.
423    pub fn with_applied<F, T>(&self, operation: F) -> Result<T, DeclaredError>
424    where
425        F: FnOnce() -> T,
426    {
427        let guard = self.install()?;
428        let outcome = operation();
429        guard.release().map(|()| outcome)
430    }
431}
432
433/// Holds declared aspects installed until released.
434///
435/// Not `Send`: it restores the thread it was created on, and the WOW64 revert
436/// token is meaningless anywhere else. Moving one to another thread would
437/// revert *that* thread's redirection using a cookie minted for a different
438/// one.
439///
440/// As with [`TransactionGuard`], that property is a consequence of a field
441/// type rather than an explicit bound -- `redirection` is a raw pointer, and
442/// raw pointers are not `Send`. Replacing it with an integer newtype would
443/// silently make this guard `Send` and this paragraph false, so the claim is
444/// pinned by a test rather than left as prose:
445///
446/// ```compile_fail,E0277
447/// fn assert_send<T: Send>() {}
448/// assert_send::<windows_thread_ambient_sys::declared::DeclaredGuard>();
449/// ```
450///
451/// [`TransactionGuard`]: crate::transaction::TransactionGuard
452#[must_use = "dropping the guard restores the aspects but discards any failure to do so"]
453#[derive(Debug)]
454pub struct DeclaredGuard {
455    background: Option<BackgroundMode>,
456    memory: Option<MemoryPriority>,
457    redirection: Option<*mut core::ffi::c_void>,
458    released: bool,
459}
460
461impl DeclaredGuard {
462    /// Restore every installed aspect, in exact reverse order.
463    ///
464    /// # Errors
465    ///
466    /// Returns the **first** [`DeclaredError`] encountered, having still
467    /// attempted every remaining restoration -- stopping early would leave more
468    /// of the thread contaminated than necessary.
469    pub fn release(mut self) -> Result<(), DeclaredError> {
470        self.released = true;
471        Self::restore(self.background, self.memory, self.redirection)
472    }
473
474    fn restore(
475        background: Option<BackgroundMode>,
476        memory: Option<MemoryPriority>,
477        redirection: Option<*mut core::ffi::c_void>,
478    ) -> Result<(), DeclaredError> {
479        let mut failure = None;
480        if let Some(old) = redirection {
481            // SAFETY: `old` is the value the matching disable call produced.
482            let ok = unsafe { Wow64RevertWow64FsRedirection(old) };
483            if ok == 0 {
484                failure = Some(DeclaredError::new(DeclaredAspect::Wow64Redirection));
485            }
486        }
487        if let Some(previous) = memory
488            && let Err(error) = previous.install()
489        {
490            failure = failure.or(Some(error));
491        }
492        if let Some(mode) = background
493            && let Err(error) = mode.inverse().install()
494        {
495            failure = failure.or(Some(error));
496        }
497        match failure {
498            Some(error) => Err(error),
499            None => Ok(()),
500        }
501    }
502}
503
504impl Drop for DeclaredGuard {
505    fn drop(&mut self) {
506        if !self.released {
507            // Best effort: a destructor has no caller to report to.
508            let _ = Self::restore(self.background, self.memory, self.redirection);
509        }
510    }
511}
512
513fn release_background(background: Option<BackgroundMode>) {
514    if let Some(mode) = background {
515        let _ = mode.inverse().install();
516    }
517}
518
519fn release_memory(memory: Option<MemoryPriority>) {
520    if let Some(previous) = memory {
521        let _ = previous.install();
522    }
523}
524
525#[cfg(test)]
526mod tests;