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;