Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: Load synch functions together #100710

Merged
merged 3 commits into from
Aug 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions library/std/src/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ pub const IPV6_ADD_MEMBERSHIP: c_int = 12;
pub const IPV6_DROP_MEMBERSHIP: c_int = 13;
pub const MSG_PEEK: c_int = 0x2;

pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = 0x800;

#[repr(C)]
#[derive(Copy, Clone)]
pub struct linger {
Expand Down Expand Up @@ -1030,6 +1032,7 @@ extern "system" {
pub fn GetProcAddress(handle: HMODULE, name: LPCSTR) -> *mut c_void;
pub fn GetModuleHandleA(lpModuleName: LPCSTR) -> HMODULE;
pub fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE;
pub fn LoadLibraryExA(lplibfilename: *const i8, hfile: HANDLE, dwflags: u32) -> HINSTANCE;

pub fn GetSystemTimeAsFileTime(lpSystemTimeAsFileTime: LPFILETIME);
pub fn GetSystemInfo(lpSystemInfo: LPSYSTEM_INFO);
Expand Down Expand Up @@ -1250,21 +1253,16 @@ compat_fn_with_fallback! {
}
}

compat_fn_with_fallback! {
pub static SYNCH_API: &CStr = ansi_str!("api-ms-win-core-synch-l1-2-0");
#[allow(unused)]
fn WakeByAddressSingle(Address: LPVOID) -> () {
// This fallback is currently tightly coupled to its use in Parker::unpark.
//
// FIXME: If `WakeByAddressSingle` needs to be used anywhere other than
// Parker::unpark then this fallback will be wrong and will need to be decoupled.
crate::sys::windows::thread_parker::unpark_keyed_event(Address)
}
compat_fn_optional! {
crate::sys::compat::load_synch_functions();
pub fn WaitOnAddress(
Address: LPVOID,
CompareAddress: LPVOID,
AddressSize: SIZE_T,
dwMilliseconds: DWORD
);
pub fn WakeByAddressSingle(Address: LPVOID);
}
pub use crate::sys::compat::WaitOnAddress;
// Change exported name of `WakeByAddressSingle` to make the strange fallback
// behaviour clear.
pub use WakeByAddressSingle::call as wake_by_address_single_or_unpark_keyed_event;

compat_fn_with_fallback! {
pub static NTDLL: &CStr = ansi_str!("ntdll");
Expand Down
121 changes: 69 additions & 52 deletions library/std/src/sys/windows/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

use crate::ffi::{c_void, CStr};
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sys::c;

/// Helper macro for creating CStrs from literals and symbol names.
Expand Down Expand Up @@ -74,6 +75,20 @@ impl Module {
NonNull::new(module).map(Self)
}

/// Load the library (if not already loaded)
///
/// # Safety
///
/// The module must not be unloaded.
pub unsafe fn load_system_library(name: &CStr) -> Option<Self> {
let module = c::LoadLibraryExA(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So previously we loaded this via GetModuleHandle no? Is there a reason to change? IIUC this one is UB (well, deadlock in practice) to call with the loader lock held, which is slightly unfortunate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(That said if theres a reason to use this instead, that's probably fine)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking into #78444 and yeah, it should properly be LoadLibrary because the only two dlls that are guaranteed loaded are ntdll and kernel32. But then again "api-ms-win-core-synch-l1-2-0" is a kind of a virtual dll that may resolve to kernel32 anyway (or might not).

My thought was it shouldn't be too much of an issue in practice considering that using synchronization while the loader lock is held is already very sketchy and that in practice the required module will (hopefully) already be loaded.

That said, I'd be ok with reverting this part if it's too controversial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, fair enough, I think we can keep it as is and see if anybody ends up complaining. This should only be triggered (I think) if people actually need to block, anyway.

name.as_ptr(),
crate::ptr::null_mut(),
c::LOAD_LIBRARY_SEARCH_SYSTEM32,
);
NonNull::new(module).map(Self)
}

// Try to get the address of a function.
pub fn proc_address(self, name: &CStr) -> Option<NonNull<c_void>> {
// SAFETY:
Expand Down Expand Up @@ -144,61 +159,63 @@ macro_rules! compat_fn_with_fallback {
)*)
}

/// Optionally load `WaitOnAddress`.
/// Unlike the dynamic loading described above, this does not have a fallback.
/// Optionally loaded functions.
///
/// This is rexported from sys::c. You should prefer to import
/// from there in case this changes again in the future.
pub mod WaitOnAddress {
use super::*;
use crate::mem;
use crate::ptr;
use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::sys::c;

static MODULE_NAME: &CStr = ansi_str!("api-ms-win-core-synch-l1-2-0");
static SYMBOL_NAME: &CStr = ansi_str!("WaitOnAddress");

// WaitOnAddress function signature.
type F = unsafe extern "system" fn(
Address: c::LPVOID,
CompareAddress: c::LPVOID,
AddressSize: c::SIZE_T,
dwMilliseconds: c::DWORD,
);

// A place to store the loaded function atomically.
static WAIT_ON_ADDRESS: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());

// We can skip trying to load again if we already tried.
static LOAD_MODULE: AtomicBool = AtomicBool::new(true);

#[inline(always)]
pub fn option() -> Option<F> {
let f = WAIT_ON_ADDRESS.load(Ordering::Acquire);
if !f.is_null() { Some(unsafe { mem::transmute(f) }) } else { try_load() }
}
/// Actual loading of the function defers to $load_functions.
macro_rules! compat_fn_optional {
($load_functions:expr;
$(
$(#[$meta:meta])*
$vis:vis fn $symbol:ident($($argname:ident: $argtype:ty),*) $(-> $rettype:ty)?;
)+) => (
$(
pub mod $symbol {
use super::*;
use crate::ffi::c_void;
use crate::mem;
use crate::ptr::{self, NonNull};
use crate::sync::atomic::{AtomicPtr, Ordering};

pub(in crate::sys) static PTR: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());

type F = unsafe extern "system" fn($($argtype),*) $(-> $rettype)?;

#[inline(always)]
pub fn option() -> Option<F> {
let f = PTR.load(Ordering::Acquire);
if !f.is_null() { Some(unsafe { mem::transmute(f) }) } else { try_load() }
}

#[cold]
fn try_load() -> Option<F> {
if LOAD_MODULE.load(Ordering::Acquire) {
// load the module
let mut wait_on_address = None;
if let Some(func) = try_load_inner() {
WAIT_ON_ADDRESS.store(func.as_ptr(), Ordering::Release);
wait_on_address = Some(unsafe { mem::transmute(func) });
#[cold]
fn try_load() -> Option<F> {
$load_functions;
NonNull::new(PTR.load(Ordering::Acquire)).map(|f| unsafe { mem::transmute(f) })
}
}
// Don't try to load the module again even if loading failed.
LOAD_MODULE.store(false, Ordering::Release);
wait_on_address
} else {
None
}
}
)+
)
}

// In the future this could be a `try` block but until then I think it's a
// little bit cleaner as a separate function.
fn try_load_inner() -> Option<NonNull<c_void>> {
unsafe { Module::new(MODULE_NAME)?.proc_address(SYMBOL_NAME) }
/// Load all needed functions from "api-ms-win-core-synch-l1-2-0".
pub(super) fn load_synch_functions() {
fn try_load() -> Option<()> {
const MODULE_NAME: &CStr = ansi_str!("api-ms-win-core-synch-l1-2-0");
const WAIT_ON_ADDRESS: &CStr = ansi_str!("WaitOnAddress");
const WAKE_BY_ADDRESS_SINGLE: &CStr = ansi_str!("WakeByAddressSingle");

// Try loading the library and all the required functions.
// If any step fails, then they all fail.
let library = unsafe { Module::load_system_library(MODULE_NAME) }?;
let wait_on_address = library.proc_address(WAIT_ON_ADDRESS)?;
let wake_by_address_single = library.proc_address(WAKE_BY_ADDRESS_SINGLE)?;

c::WaitOnAddress::PTR.store(wait_on_address.as_ptr(), Ordering::Release);
c::WakeByAddressSingle::PTR.store(wake_by_address_single.as_ptr(), Ordering::Release);
Some(())
}

// Try to load the module but skip loading if a previous attempt failed.
static LOAD_MODULE: AtomicBool = AtomicBool::new(true);
let module_loaded = LOAD_MODULE.load(Ordering::Acquire) && try_load().is_some();
LOAD_MODULE.store(module_loaded, Ordering::Release)
}
27 changes: 12 additions & 15 deletions library/std/src/sys/windows/thread_parker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,18 @@ impl Parker {
// with park().
if self.state.swap(NOTIFIED, Release) == PARKED {
unsafe {
// This calls either WakeByAddressSingle or unpark_keyed_event (see below).
c::wake_by_address_single_or_unpark_keyed_event(self.ptr());
if let Some(wake_by_address_single) = c::WakeByAddressSingle::option() {
wake_by_address_single(self.ptr());
} else {
// If we run NtReleaseKeyedEvent before the waiting thread runs
// NtWaitForKeyedEvent, this (shortly) blocks until we can wake it up.
// If the waiting thread wakes up before we run NtReleaseKeyedEvent
// (e.g. due to a timeout), this blocks until we do wake up a thread.
// To prevent this thread from blocking indefinitely in that case,
// park_impl() will, after seeing the state set to NOTIFIED after
// waking up, call NtWaitForKeyedEvent again to unblock us.
c::NtReleaseKeyedEvent(keyed_event_handle(), self.ptr(), 0, ptr::null_mut());
}
}
}
}
Expand All @@ -209,19 +219,6 @@ impl Parker {
}
}

// This function signature makes it compatible with c::WakeByAddressSingle
// so that it can be used as a fallback for that function.
pub unsafe extern "C" fn unpark_keyed_event(address: c::LPVOID) {
// If we run NtReleaseKeyedEvent before the waiting thread runs
// NtWaitForKeyedEvent, this (shortly) blocks until we can wake it up.
// If the waiting thread wakes up before we run NtReleaseKeyedEvent
// (e.g. due to a timeout), this blocks until we do wake up a thread.
// To prevent this thread from blocking indefinitely in that case,
// park_impl() will, after seeing the state set to NOTIFIED after
// waking up, call NtWaitForKeyedEvent again to unblock us.
c::NtReleaseKeyedEvent(keyed_event_handle(), address, 0, ptr::null_mut());
}

fn keyed_event_handle() -> c::HANDLE {
const INVALID: c::HANDLE = ptr::invalid_mut(!0);
static HANDLE: AtomicPtr<libc::c_void> = AtomicPtr::new(INVALID);
Expand Down