|
| 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 | + |
1 | 60 | use crate::convert::TryFrom;
|
2 | 61 | use crate::ptr;
|
3 | 62 | use crate::sync::atomic::{
|
@@ -34,7 +93,7 @@ const NOTIFIED: i8 = 1;
|
34 | 93 | //
|
35 | 94 | // This is done with a release-acquire synchronization, by using
|
36 | 95 | // 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. |
38 | 97 | impl Parker {
|
39 | 98 | pub fn new() -> Self {
|
40 | 99 | Self { state: AtomicI8::new(EMPTY) }
|
|
0 commit comments