Skip to content
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ tokio = { version = "1.47.1", features = [ "rt-multi-thread" ] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[target.'cfg(unix)'.dependencies]
libc = "0.2"

[dev-dependencies]
ntest = "0.9"
tokio = { version = "1.47.1", features = ["test-util", "macros"] }
Expand Down
60 changes: 59 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1547,10 +1547,68 @@ impl Record {
fn runtime() -> Result<&'static Runtime> {
static RUNTIME: OnceCell<Runtime> = OnceCell::new();

let rt = RUNTIME.get_or_try_init(Runtime::new).unwrap();
let rt = RUNTIME
.get_or_try_init(|| {
pin_module_in_memory();
Runtime::new()
})
.unwrap();
Ok(rt)
}

/// Keep this library mapped for the lifetime of the process.
///
/// Node.js unloads a native addon when the worker thread that loaded it exits
/// (`Environment::~Environment` closes every addon a non-main thread loaded),
/// unless something else still holds the library open. This addon owns
/// process-wide threads that outlive any single environment: the tokio runtime
/// behind the synchronous API and the query timeout thread. If the library is
/// unmapped underneath them, they fault on the next instruction they execute,
/// which is how `ava` (test files run in worker threads) crashed with an access
/// violation on Windows. Every process-wide thread must be spawned after this
/// has run.
pub(crate) fn pin_module_in_memory() {
static PIN: std::sync::Once = std::sync::Once::new();
PIN.call_once(|| {
let addr_in_module = pin_module_in_memory as *const ();

#[cfg(windows)]
unsafe {
#[link(name = "kernel32")]
extern "system" {
fn GetModuleHandleExW(
flags: u32,
module_name: *const u16,
module: *mut *mut std::ffi::c_void,
) -> i32;
}
const GET_MODULE_HANDLE_EX_FLAG_PIN: u32 = 0x1;
const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 0x4;
let mut module = std::ptr::null_mut();
GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
addr_in_module as *const u16,
&mut module,
);
}

#[cfg(unix)]
unsafe {
let mut info: libc::Dl_info = std::mem::zeroed();
if libc::dladdr(addr_in_module as *const libc::c_void, &mut info) != 0
&& !info.dli_fname.is_null()
{
// Re-open our own image with RTLD_NODELETE so dlclose() never
// unmaps it. The handle is intentionally leaked.
libc::dlopen(
info.dli_fname,
libc::RTLD_NOW | libc::RTLD_NOLOAD | libc::RTLD_NODELETE,
);
}
}
});
}

fn map_row(
env: &Env,
column_names: &[std::ffi::CString],
Expand Down
5 changes: 4 additions & 1 deletion src/query_timeout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ impl QueryTimeoutManager {
/// Returns the process-wide timeout manager, spawning its single
/// background thread on first use.
pub fn global() -> &'static QueryTimeoutManager {
GLOBAL.get_or_init(QueryTimeoutManager::new)
GLOBAL.get_or_init(|| {
crate::pin_module_in_memory();
QueryTimeoutManager::new()
})
}

pub fn new() -> Self {
Expand Down
Loading