use std::{
cell::RefCell,
collections::HashMap,
ptr,
sync::{Arc, LazyLock, Mutex, MutexGuard},
thread::{self, ThreadId},
};
#[cfg(all(feature = "napi4", not(feature = "noop")))]
use crate::bindgen_runtime::module_register::{current_custom_gc_handle, CustomGcHandle};
use crate::{sys, Error, Result, Status};
#[derive(Clone, Copy)]
enum NativeBorrowKind {
Shared,
Exclusive,
}
#[derive(Default)]
struct NativeBorrowState {
shared: usize,
exclusive: bool,
}
static NATIVE_BORROWS: LazyLock<Mutex<HashMap<usize, NativeBorrowState>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
thread_local! {
static NATIVE_BORROW_SCOPES: RefCell<Vec<*const Mutex<NativeBorrowStorage>>> =
const { RefCell::new(Vec::new()) };
static DEFERRED_NATIVE_BORROW_SCOPES: RefCell<Vec<FinishedNativeBorrowScope>> =
const { RefCell::new(Vec::new()) };
}
#[derive(Default)]
struct NativeBorrowStorage {
guards: Vec<NativeBorrowGuard>,
roots: Vec<NativeBorrowRoot>,
borrowed_values: Vec<NativeBorrowedValue>,
root_values: bool,
owner_thread: Option<ThreadId>,
closed: bool,
open_leases: usize,
#[cfg(all(feature = "napi4", not(feature = "noop")))]
custom_gc: Option<Arc<CustomGcHandle>>,
}
unsafe impl Send for NativeBorrowStorage {}
unsafe impl Sync for NativeBorrowStorage {}
struct NativeBorrowRoot {
env: sys::napi_env,
reference: sys::napi_ref,
}
struct NativeBorrowedValue {
env: sys::napi_env,
value: sys::napi_value,
}
fn lock_storage(storage: &Mutex<NativeBorrowStorage>) -> MutexGuard<'_, NativeBorrowStorage> {
storage
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
impl NativeBorrowStorage {
fn release(&mut self, expected_env: Option<sys::napi_env>) {
self.guards.clear();
if self.roots.is_empty() {
return;
}
if self.owner_thread != Some(thread::current().id()) {
std::process::abort();
}
for root in self.roots.drain(..) {
if expected_env.is_some_and(|expected_env| expected_env != root.env) {
std::process::abort();
}
let mut ref_count = 0;
let unref_status =
unsafe { sys::napi_reference_unref(root.env, root.reference, &mut ref_count) };
let delete_status = unsafe { sys::napi_delete_reference(root.env, root.reference) };
if cfg!(debug_assertions)
&& (unref_status != sys::Status::napi_ok || delete_status != sys::Status::napi_ok)
{
crate::bindgen_runtime::catch_unwind_safely(|| {
eprintln!(
"Failed to release generated native borrow root: unref={}, delete={}",
Status::from(unref_status),
Status::from(delete_status)
);
});
}
}
}
fn release_or_delegate(&mut self) {
self.guards.clear();
self.borrowed_values.clear();
if self.roots.is_empty() {
return;
}
#[cfg(all(feature = "napi4", not(feature = "noop")))]
if let Some(handle) = self.custom_gc.clone() {
let roots = std::mem::take(&mut self.roots);
handle.with_read_aborted(|aborted| {
if aborted {
return;
}
for root in roots {
let status = unsafe {
sys::napi_call_threadsafe_function(handle.get_raw(), root.reference.cast(), 1)
};
if cfg!(debug_assertions)
&& status != sys::Status::napi_ok
&& status != sys::Status::napi_closing
{
crate::bindgen_runtime::catch_unwind_safely(|| {
eprintln!(
"Failed to enqueue native borrow root for release: {}",
Status::from(status)
);
});
}
}
});
return;
}
self.roots.clear();
}
fn ensure_rooted(&mut self) -> Result<()> {
if self.root_values {
return Ok(());
}
self.root_values = true;
self.owner_thread = Some(thread::current().id());
for borrowed in std::mem::take(&mut self.borrowed_values) {
let mut reference = ptr::null_mut();
let status =
unsafe { sys::napi_create_reference(borrowed.env, borrowed.value, 1, &mut reference) };
if status != sys::Status::napi_ok {
return Err(Error::new(
Status::from(status),
"Failed to root JavaScript value for a native borrow".to_owned(),
));
}
self.capture_custom_gc();
self.roots.push(NativeBorrowRoot {
env: borrowed.env,
reference,
});
}
Ok(())
}
#[cfg(all(feature = "napi4", not(feature = "noop")))]
fn capture_custom_gc(&mut self) {
if self.custom_gc.is_none() {
self.custom_gc = current_custom_gc_handle();
}
}
#[cfg(not(all(feature = "napi4", not(feature = "noop"))))]
fn capture_custom_gc(&mut self) {}
}
fn close_scope(storage: &Mutex<NativeBorrowStorage>, settle_env: Option<sys::napi_env>) {
let mut storage = lock_storage(storage);
storage.closed = true;
if storage.open_leases != 0 {
return;
}
match settle_env {
Some(env) => storage.release(Some(env)),
None => storage.release_or_delegate(),
}
}
fn close_scope_in_frame(storage: &Mutex<NativeBorrowStorage>) {
let mut storage = lock_storage(storage);
storage.closed = true;
if storage.open_leases != 0 {
return;
}
storage.release(None);
}
fn release_lease(storage: &Mutex<NativeBorrowStorage>, settle_env: Option<sys::napi_env>) {
let mut storage = lock_storage(storage);
debug_assert!(
storage.open_leases != 0,
"native borrow lease released more times than it was claimed"
);
storage.open_leases = storage.open_leases.saturating_sub(1);
if !storage.closed || storage.open_leases != 0 {
return;
}
match settle_env {
Some(env) => storage.release(Some(env)),
None => storage.release_or_delegate(),
}
}
#[doc(hidden)]
pub struct NativeBorrowBarrier {
saved_deferred: Vec<FinishedNativeBorrowScope>,
_not_send: std::marker::PhantomData<std::rc::Rc<()>>,
}
impl NativeBorrowBarrier {
#[doc(hidden)]
pub fn new() -> Self {
NATIVE_BORROW_SCOPES.with(|scopes| scopes.borrow_mut().push(ptr::null()));
let saved_deferred =
DEFERRED_NATIVE_BORROW_SCOPES.with(|scopes| std::mem::take(&mut *scopes.borrow_mut()));
Self {
saved_deferred,
_not_send: std::marker::PhantomData,
}
}
}
impl Drop for NativeBorrowBarrier {
fn drop(&mut self) {
NATIVE_BORROW_SCOPES.with(|scopes| {
let barrier = scopes
.borrow_mut()
.pop()
.expect("native borrow barriers must be dropped in stack order");
assert!(
barrier.is_null(),
"native borrow barriers must not overlap conversion scopes"
);
});
DEFERRED_NATIVE_BORROW_SCOPES.with(|scopes| {
let mut scopes = scopes.borrow_mut();
scopes.clear();
*scopes = std::mem::take(&mut self.saved_deferred);
});
}
}
#[doc(hidden)]
pub struct NativeBorrowScope {
storage: Arc<Mutex<NativeBorrowStorage>>,
collecting: bool,
_not_send: std::marker::PhantomData<std::rc::Rc<()>>,
}
impl NativeBorrowScope {
#[doc(hidden)]
pub unsafe fn new() -> Self {
unsafe { Self::new_inner(false) }
}
#[doc(hidden)]
pub unsafe fn new_async() -> Self {
unsafe { Self::new_inner(true) }
}
unsafe fn new_inner(root_values: bool) -> Self {
let storage = Arc::new(Mutex::new(NativeBorrowStorage {
root_values,
owner_thread: root_values.then(|| thread::current().id()),
..Default::default()
}));
NATIVE_BORROW_SCOPES.with(|scopes| scopes.borrow_mut().push(Arc::as_ptr(&storage)));
Self {
storage,
collecting: true,
_not_send: std::marker::PhantomData,
}
}
#[doc(hidden)]
pub fn finish(&mut self) {
if !self.collecting {
return;
}
let expected = Arc::as_ptr(&self.storage);
NATIVE_BORROW_SCOPES.with(|scopes| {
let actual = scopes
.borrow_mut()
.pop()
.expect("native borrow scopes must be finished in stack order");
assert_eq!(actual, expected, "native borrow scopes must not overlap");
});
self.collecting = false;
}
pub(crate) fn finish_sendable(self) -> FinishedNativeBorrowScope {
let mut this = std::mem::ManuallyDrop::new(self);
this.finish();
let storage = unsafe { ptr::read(&this.storage) };
FinishedNativeBorrowScope { storage }
}
#[doc(hidden)]
pub fn release(mut self, env: sys::napi_env) {
self.finish();
close_scope(&self.storage, Some(env));
}
}
#[doc(hidden)]
pub struct FinishedNativeBorrowScope {
storage: Arc<Mutex<NativeBorrowStorage>>,
}
impl FinishedNativeBorrowScope {
#[doc(hidden)]
pub fn release(self, env: sys::napi_env) {
close_scope(&self.storage, Some(env));
}
pub(crate) fn abandon(self) {
close_scope(&self.storage, None);
}
}
impl Drop for FinishedNativeBorrowScope {
fn drop(&mut self) {
close_scope(&self.storage, None);
}
}
impl Drop for NativeBorrowScope {
fn drop(&mut self) {
if self.collecting {
self.finish();
close_scope_in_frame(&self.storage);
} else {
close_scope(&self.storage, None);
}
}
}
#[doc(hidden)]
pub struct NativeBorrowLease {
storage: Option<Arc<Mutex<NativeBorrowStorage>>>,
}
impl NativeBorrowLease {
#[doc(hidden)]
pub fn release(mut self, env: sys::napi_env) {
if let Some(storage) = self.storage.take() {
release_lease(&storage, Some(env));
}
}
}
unsafe impl Send for NativeBorrowLease {}
impl Drop for NativeBorrowLease {
fn drop(&mut self) {
if let Some(storage) = self.storage.take() {
release_lease(&storage, None);
}
}
}
#[doc(hidden)]
pub struct DeferredNativeBorrowScopeGuard {
storage: *const Mutex<NativeBorrowStorage>,
env: sys::napi_env,
}
#[doc(hidden)]
pub fn defer_native_borrow_scope(
scope: NativeBorrowScope,
env: sys::napi_env,
) -> DeferredNativeBorrowScopeGuard {
let scope = scope.finish_sendable();
let storage = Arc::as_ptr(&scope.storage);
DEFERRED_NATIVE_BORROW_SCOPES.with(|scopes| scopes.borrow_mut().push(scope));
DeferredNativeBorrowScopeGuard { storage, env }
}
pub(crate) fn claim_deferred_native_borrow_lease() -> Result<Option<NativeBorrowLease>> {
let storage = DEFERRED_NATIVE_BORROW_SCOPES
.with(|scopes| scopes.borrow().last().map(|scope| scope.storage.clone()));
match storage {
Some(storage) => {
{
let mut storage = lock_storage(&storage);
storage.ensure_rooted()?;
storage.open_leases += 1;
}
Ok(Some(NativeBorrowLease {
storage: Some(storage),
}))
}
None => Ok(None),
}
}
impl Drop for DeferredNativeBorrowScopeGuard {
fn drop(&mut self) {
let scope = DEFERRED_NATIVE_BORROW_SCOPES.with(|scopes| {
let mut scopes = scopes.borrow_mut();
if scopes
.last()
.is_some_and(|scope| Arc::as_ptr(&scope.storage) == self.storage)
{
scopes.pop()
} else {
None
}
});
if let Some(scope) = scope {
scope.release(self.env);
}
}
}
#[doc(hidden)]
pub struct NativeBorrowScopeRelease {
scope: Option<FinishedNativeBorrowScope>,
}
impl NativeBorrowScopeRelease {
#[doc(hidden)]
pub fn new(scope: NativeBorrowScope) -> Self {
Self {
scope: Some(scope.finish_sendable()),
}
}
#[doc(hidden)]
pub fn release(mut self, env: sys::napi_env) {
if let Some(scope) = self.scope.take() {
scope.release(env);
}
}
}
impl Drop for NativeBorrowScopeRelease {
fn drop(&mut self) {
if let Some(scope) = self.scope.take() {
scope.abandon();
}
}
}
#[doc(hidden)]
pub struct NativeBorrowGuard {
key: usize,
kind: NativeBorrowKind,
}
impl NativeBorrowGuard {
fn acquire<T>(value: *mut T, kind: NativeBorrowKind) -> Result<Self> {
if value.is_null() {
return Err(Error::new(
Status::InvalidArg,
"Cannot borrow a null native value".to_owned(),
));
}
let key = value as usize;
let mut borrows = NATIVE_BORROWS
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let state = borrows.entry(key).or_default();
let conflict = match kind {
NativeBorrowKind::Shared => state.exclusive,
NativeBorrowKind::Exclusive => state.exclusive || state.shared != 0,
};
if conflict {
return Err(Error::new(
Status::InvalidArg,
"The same native value cannot be borrowed mutably while another borrow is active"
.to_owned(),
));
}
match kind {
NativeBorrowKind::Shared => {
state.shared = state
.shared
.checked_add(1)
.expect("native shared borrow count overflow");
}
NativeBorrowKind::Exclusive => state.exclusive = true,
}
Ok(Self { key, kind })
}
}
impl Drop for NativeBorrowGuard {
fn drop(&mut self) {
let mut borrows = NATIVE_BORROWS
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let state = borrows
.get_mut(&self.key)
.expect("native borrow guard must have a registered state");
match self.kind {
NativeBorrowKind::Shared => {
state.shared = state
.shared
.checked_sub(1)
.expect("native shared borrow count underflow");
}
NativeBorrowKind::Exclusive => state.exclusive = false,
}
if state.shared == 0 && !state.exclusive {
borrows.remove(&self.key);
}
}
}
#[doc(hidden)]
pub fn register_native_borrow<T>(value: *mut T, mutable: bool) -> Result<()> {
let scope = current_native_borrow_scope()?;
let guard = NativeBorrowGuard::acquire(value, native_borrow_kind(mutable))?;
lock_storage(unsafe { &*scope }).guards.push(guard);
Ok(())
}
#[doc(hidden)]
pub unsafe fn register_native_borrow_with_value<T>(
env: sys::napi_env,
napi_val: sys::napi_value,
value: *mut T,
mutable: bool,
) -> Result<()> {
super::check_class_identity::<T>(value.cast(), super::class_identity::short_type_name::<T>)?;
let scope = current_native_borrow_scope()?;
unsafe { register_native_borrow_with_value_in_scope(scope, env, napi_val, value, mutable) }
}
#[allow(dead_code)]
pub(crate) unsafe fn register_legacy_native_borrow_with_value<T>(
env: sys::napi_env,
napi_val: sys::napi_value,
value: *mut T,
mutable: bool,
) -> Result<()> {
match current_native_borrow_scope_entry() {
None => Ok(()),
Some(scope) if scope.is_null() => Err(missing_native_borrow_scope_error()),
Some(scope) => unsafe {
register_native_borrow_with_value_in_scope(scope, env, napi_val, value, mutable)
},
}
}
unsafe fn register_native_borrow_with_value_in_scope<T>(
scope: *const Mutex<NativeBorrowStorage>,
env: sys::napi_env,
napi_val: sys::napi_value,
value: *mut T,
mutable: bool,
) -> Result<()> {
let guard = NativeBorrowGuard::acquire(value, native_borrow_kind(mutable))?;
let mut storage = lock_storage(unsafe { &*scope });
if storage.root_values {
if napi_val.is_null() {
return Err(Error::new(
Status::InvalidArg,
"Cannot root a null JavaScript value for a native borrow".to_owned(),
));
}
let mut reference = ptr::null_mut();
let status = unsafe { sys::napi_create_reference(env, napi_val, 1, &mut reference) };
if status != sys::Status::napi_ok {
return Err(Error::new(
Status::from(status),
"Failed to root JavaScript value for a native borrow".to_owned(),
));
}
storage.capture_custom_gc();
storage.roots.push(NativeBorrowRoot { env, reference });
} else if !napi_val.is_null() {
storage.borrowed_values.push(NativeBorrowedValue {
env,
value: napi_val,
});
}
storage.guards.push(guard);
Ok(())
}
fn current_native_borrow_scope() -> Result<*const Mutex<NativeBorrowStorage>> {
current_native_borrow_scope_entry()
.filter(|scope| !scope.is_null())
.ok_or_else(missing_native_borrow_scope_error)
}
fn current_native_borrow_scope_entry() -> Option<*const Mutex<NativeBorrowStorage>> {
NATIVE_BORROW_SCOPES.with(|scopes| scopes.borrow().last().copied())
}
fn missing_native_borrow_scope_error() -> Error {
Error::new(
Status::InvalidArg,
"Native references can only be created by generated callback argument conversion".to_owned(),
)
}
fn native_borrow_kind(mutable: bool) -> NativeBorrowKind {
if mutable {
NativeBorrowKind::Exclusive
} else {
NativeBorrowKind::Shared
}
}
#[doc(hidden)]
pub fn acquire_native_borrow<T>(value: *mut T, mutable: bool) -> Result<NativeBorrowGuard> {
NativeBorrowGuard::acquire(
value,
if mutable {
NativeBorrowKind::Exclusive
} else {
NativeBorrowKind::Shared
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn native_borrows_reject_aliasing_and_release_on_drop() {
let mut value = 1u32;
let value_ptr = &mut value as *mut u32;
let shared = acquire_native_borrow(value_ptr, false).unwrap();
let second_shared = acquire_native_borrow(value_ptr, false).unwrap();
assert!(acquire_native_borrow(value_ptr, true).is_err());
drop(second_shared);
drop(shared);
let exclusive = acquire_native_borrow(value_ptr, true).unwrap();
assert!(acquire_native_borrow(value_ptr, false).is_err());
assert!(acquire_native_borrow(value_ptr, true).is_err());
drop(exclusive);
assert!(acquire_native_borrow(value_ptr, true).is_ok());
}
#[test]
fn generated_native_borrows_require_a_scope() {
let mut value = 1u32;
let error = register_native_borrow(&mut value, false).unwrap_err();
assert_eq!(error.status, Status::InvalidArg);
assert!(error
.reason
.contains("generated callback argument conversion"));
}
#[test]
fn legacy_native_borrows_allow_an_absent_scope() {
let mut value = 1u32;
unsafe {
register_legacy_native_borrow_with_value(ptr::null_mut(), ptr::null_mut(), &mut value, false)
}
.unwrap();
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn legacy_native_borrows_join_an_active_scope() {
let mut value = 1u32;
let scope = unsafe { NativeBorrowScope::new() };
unsafe {
register_legacy_native_borrow_with_value(ptr::null_mut(), ptr::null_mut(), &mut value, false)
}
.unwrap();
assert!(acquire_native_borrow(&mut value, true).is_err());
drop(scope);
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn legacy_native_borrows_respect_callback_barriers() {
let mut outer_value = 1u32;
let mut reentrant_value = 2u32;
let outer_scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut outer_value, false).unwrap();
{
let _barrier = NativeBorrowBarrier::new();
let error = unsafe {
register_legacy_native_borrow_with_value(
ptr::null_mut(),
ptr::null_mut(),
&mut reentrant_value,
false,
)
}
.unwrap_err();
assert_eq!(error.status, Status::InvalidArg);
assert!(error
.reason
.contains("generated callback argument conversion"));
}
assert!(register_native_borrow(&mut outer_value, false).is_ok());
drop(outer_scope);
}
#[test]
fn callback_barrier_hides_outer_conversion_scope() {
let mut outer_value = 1u32;
let mut inner_value = 2u32;
let mut outer_scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut outer_value, false).unwrap();
{
let _barrier = NativeBorrowBarrier::new();
assert!(register_native_borrow(&mut inner_value, false).is_err());
let mut inner_scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut inner_value, false).unwrap();
inner_scope.finish();
}
register_native_borrow(&mut outer_value, false).unwrap();
outer_scope.finish();
}
#[test]
fn deferred_scope_lease_outlives_guard_reclaim() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
let guard = defer_native_borrow_scope(scope, ptr::null_mut());
let lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("deferred scope must lease");
drop(guard);
assert!(acquire_native_borrow(&mut value, true).is_err());
drop(lease);
assert!(acquire_native_borrow(&mut value, true).is_ok());
assert!(claim_deferred_native_borrow_lease().unwrap().is_none());
}
#[test]
fn deferred_scope_serves_multiple_leases_until_last_release() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
let guard = defer_native_borrow_scope(scope, ptr::null_mut());
let first = claim_deferred_native_borrow_lease()
.unwrap()
.expect("first sink must lease");
let second = claim_deferred_native_borrow_lease()
.unwrap()
.expect("second sink must lease the same scope");
drop(guard);
drop(first);
assert!(acquire_native_borrow(&mut value, true).is_err());
drop(second);
assert!(acquire_native_borrow(&mut value, true).is_ok());
assert!(claim_deferred_native_borrow_lease().unwrap().is_none());
}
#[test]
fn lease_released_with_env_still_waits_for_scope_close() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
let guard = defer_native_borrow_scope(scope, ptr::null_mut());
let lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("deferred scope must lease");
lease.release(ptr::null_mut());
assert!(acquire_native_borrow(&mut value, true).is_err());
drop(guard);
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn last_lease_dropped_on_foreign_thread_releases_guards() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
let guard = defer_native_borrow_scope(scope, ptr::null_mut());
let lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("deferred scope must lease");
drop(guard);
std::thread::spawn(move || drop(lease))
.join()
.expect("foreign-thread lease drop must not panic or abort");
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn rooted_last_lease_dropped_on_foreign_thread_delegates_roots() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
{
let mut storage = lock_storage(&scope.storage);
storage.roots.push(NativeBorrowRoot {
env: ptr::null_mut(),
reference: ptr::null_mut(),
});
storage.owner_thread = Some(thread::current().id());
}
let guard = defer_native_borrow_scope(scope, ptr::null_mut());
let lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("deferred scope must lease");
drop(guard);
std::thread::spawn(move || drop(lease))
.join()
.expect("foreign-thread lease drop must not panic or abort");
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn unclaimed_deferred_scope_is_reclaimed_by_guard() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
let guard = defer_native_borrow_scope(scope, ptr::null_mut());
drop(guard);
assert!(claim_deferred_native_borrow_lease().unwrap().is_none());
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn callback_barrier_hides_outer_deferred_scope() {
let mut outer_value = 1u32;
let mut outer_scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut outer_value, false).unwrap();
outer_scope.finish();
let outer_guard = defer_native_borrow_scope(outer_scope, ptr::null_mut());
{
let _barrier = NativeBorrowBarrier::new();
assert!(claim_deferred_native_borrow_lease().unwrap().is_none());
let mut inner_scope = unsafe { NativeBorrowScope::new() };
inner_scope.finish();
let inner_guard = defer_native_borrow_scope(inner_scope, ptr::null_mut());
let inner_lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("inner scope must lease");
drop(inner_guard);
drop(inner_lease);
assert!(claim_deferred_native_borrow_lease().unwrap().is_none());
}
let outer_lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("outer scope must lease after the barrier drops");
drop(outer_guard);
drop(outer_lease);
assert!(acquire_native_borrow(&mut outer_value, true).is_ok());
assert!(claim_deferred_native_borrow_lease().unwrap().is_none());
}
#[test]
fn deferred_scopes_reclaim_in_lifo_order() {
let mut first_value = 1u32;
let mut second_value = 2u32;
let mut outer_scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut first_value, false).unwrap();
outer_scope.finish();
let outer_guard = defer_native_borrow_scope(outer_scope, ptr::null_mut());
let mut inner_scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut second_value, false).unwrap();
inner_scope.finish();
let inner_guard = defer_native_borrow_scope(inner_scope, ptr::null_mut());
let inner_lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("inner scope must lease");
drop(inner_guard);
assert!(acquire_native_borrow(&mut second_value, true).is_err());
drop(inner_lease);
assert!(acquire_native_borrow(&mut second_value, true).is_ok());
assert!(acquire_native_borrow(&mut first_value, true).is_err());
drop(outer_guard);
assert!(acquire_native_borrow(&mut first_value, true).is_ok());
assert!(claim_deferred_native_borrow_lease().unwrap().is_none());
}
#[test]
fn rooted_scope_dropped_on_foreign_thread_abandons() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
{
let mut storage = lock_storage(&scope.storage);
storage.roots.push(NativeBorrowRoot {
env: ptr::null_mut(),
reference: ptr::null_mut(),
});
storage.owner_thread = Some(thread::current().id());
}
let scope = scope.finish_sendable();
std::thread::spawn(move || drop(scope))
.join()
.expect("foreign-thread drop must not panic or abort");
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn only_finished_scopes_are_send() {
fn assert_send<T: Send>() {}
assert_send::<FinishedNativeBorrowScope>();
assert_send::<NativeBorrowLease>();
assert_send::<NativeBorrowScopeRelease>();
}
#[test]
fn finish_sendable_drains_the_collecting_stack() {
let mut value = 1u32;
let scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
let finished = scope.finish_sendable();
assert!(current_native_borrow_scope_entry().is_none());
drop(finished);
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn uninvoked_scope_release_abandons_instead_of_calling_napi() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
{
let mut storage = lock_storage(&scope.storage);
storage.roots.push(NativeBorrowRoot {
env: ptr::null_mut(),
reference: ptr::null_mut(),
});
storage.owner_thread = Some(thread::current().id());
}
drop(NativeBorrowScopeRelease::new(scope));
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
#[test]
fn deferred_scope_with_values_claim_upgrades_and_release_clears_guards() {
let mut value = 1u32;
let mut scope = unsafe { NativeBorrowScope::new() };
register_native_borrow(&mut value, false).unwrap();
scope.finish();
let guard = defer_native_borrow_scope(scope, ptr::null_mut());
let lease = claim_deferred_native_borrow_lease()
.unwrap()
.expect("deferred scope must lease");
drop(guard);
drop(lease);
assert!(acquire_native_borrow(&mut value, true).is_ok());
}
}