Skip to content

Commit 43f844b

Browse files
committed
Add documentation to Windows thread parker implementation.
1 parent d1c1393 commit 43f844b

File tree

1 file changed

+60
-1
lines changed

1 file changed

+60
-1
lines changed

library/std/src/sys/windows/thread_parker.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,62 @@
1+
// Thread parker implementation for Windows.
2+
//
3+
// This uses WaitOnAddress and WakeByAddressSingle if available (Windows 8+).
4+
// This modern API is exactly the same as the futex syscalls the Linux thread
5+
// parker uses. When These APIs are available, the implementation of this
6+
// thread parker matches the Linux thread parker exactly.
7+
//
8+
// However, when the modern API is not available, this implementation falls
9+
// back to NT Keyed Events, which are similar, but have some important
10+
// differences. These are available since Windows XP.
11+
//
12+
// WaitOnAddress first checks the state of the thread parker to make sure it no
13+
// WakeByAddressSingle calls can be missed between updating the parker state
14+
// and calling the function.
15+
//
16+
// NtWaitForKeyedEvent does not have this option, and unconditionally blocks
17+
// without checking the parker state first. Instead, NtReleaseKeyedEvent
18+
// (unlike WakeByAddressSingle) *blocks* until it woke up a thread waiting for
19+
// it by NtWaitForKeyedEvent. This way, we can be sure no events are missed,
20+
// but we need to be careful not to block unpark() if park_timeout() was woken
21+
// up by a timeout instead of unpark().
22+
//
23+
// Unlike WaitOnAddress, NtWaitForKeyedEvent/NtReleaseKeyedEvent operate on a
24+
// HANDLE (created with NtCreateKeyedEvent). This means that we can be sure
25+
// a succesfully awoken park() was awoken by unpark() and not a
26+
// NtReleaseKeyedEvent call from some other code, as these events are not only
27+
// matched by the key (address of the parker (state)), but also by this HANDLE.
28+
// We lazily allocate this handle the first time it is needed.
29+
//
30+
// The fast path (calling park() after unpark() was already called) and the
31+
// possible states are the same for both implementations. This is used here to
32+
// make sure the fast path does not even check which API to use, but can return
33+
// right away, independent of the used API. Only the slow paths (which will
34+
// actually block/wake a thread) check which API is available and have
35+
// different implementations.
36+
//
37+
// Unfortunately, NT Keyed Events are an undocumented Windows API. However:
38+
// - This API is relatively simple with obvious behaviour, and there are
39+
// several (unofficial) articles documenting the details. [1]
40+
// - `parking_lot` has been using this API for years (on Windows versions
41+
// before Windows 8). [2] Many big projects extensively use parking_lot,
42+
// such as servo and the Rust compiler itself.
43+
// - It is the underlying API used by Windows SRW locks and Windows critical
44+
// sections. [3] [4]
45+
// - The source code of the implementations of Wine, ReactOs, and Windows XP
46+
// are available and match the expected behaviour.
47+
// - The main risk with an undocumented API is that it might change in the
48+
// future. But since we only use it for older versions of Windows, that's not
49+
// a problem.
50+
// - Even if these functions do not block or wake as we expect (which is
51+
// unlikely, see all previous points), this implementation would still be
52+
// memory safe. The NT Keyed Events API is only used to sleep/block in the
53+
// right place.
54+
//
55+
// [1]: http://www.locklessinc.com/articles/keyed_events/
56+
// [2]: https://github.com/Amanieu/parking_lot/commit/43abbc964e
57+
// [3]: https://docs.microsoft.com/en-us/archive/msdn-magazine/2012/november/windows-with-c-the-evolution-of-synchronization-in-windows-and-c
58+
// [4]: Windows Internals, Part 1, ISBN 9780735671300
59+
160
use crate::convert::TryFrom;
261
use crate::ptr;
362
use crate::sync::atomic::{
@@ -34,7 +93,7 @@ const NOTIFIED: i8 = 1;
3493
//
3594
// This is done with a release-acquire synchronization, by using
3695
// Ordering::Release when writing NOTIFIED (the 'token') in unpark(), and using
37-
// Ordering::Acquire when checking for this state in park().
96+
// Ordering::Acquire when reading this state in park() after waking up.
3897
impl Parker {
3998
pub fn new() -> Self {
4099
Self { state: AtomicI8::new(EMPTY) }

0 commit comments

Comments
 (0)