diff options
author | Aapo Alasuutari <aapo.alasuutari@gmail.com> | 2023-05-07 13:31:01 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-07 10:31:01 +0000 |
commit | 0536ae86588328b773f82d9724cd816a86217583 (patch) | |
tree | 0655e5834c1a6ef15bee02f5e0bf3347fc12f8c4 /ext/ffi/callback.rs | |
parent | b8c936076162e77039063126ee882d995f2c45de (diff) |
fix(ext/ffi): UnsafeCallback can hang with 'deno test' (#19018)
Diffstat (limited to 'ext/ffi/callback.rs')
-rw-r--r-- | ext/ffi/callback.rs | 82 |
1 files changed, 54 insertions, 28 deletions
diff --git a/ext/ffi/callback.rs b/ext/ffi/callback.rs index bd4d6a545..ef613b3ed 100644 --- a/ext/ffi/callback.rs +++ b/ext/ffi/callback.rs @@ -6,7 +6,6 @@ use crate::FfiPermissions; use crate::FfiState; use crate::ForeignFunction; use crate::PendingFfiAsyncWork; -use crate::LOCAL_ISOLATE_POINTER; use crate::MAX_SAFE_INTEGER; use crate::MIN_SAFE_INTEGER; use deno_core::error::AnyError; @@ -30,9 +29,18 @@ use std::pin::Pin; use std::ptr; use std::ptr::NonNull; use std::rc::Rc; +use std::sync::atomic; +use std::sync::atomic::AtomicU32; use std::sync::mpsc::sync_channel; use std::task::Poll; use std::task::Waker; + +static THREAD_ID_COUNTER: AtomicU32 = AtomicU32::new(1); + +thread_local! { + static LOCAL_THREAD_ID: RefCell<u32> = RefCell::new(0); +} + #[derive(Clone)] pub struct PtrSymbol { pub cif: libffi::middle::Cif, @@ -81,26 +89,16 @@ impl Resource for UnsafeCallbackResource { fn close(self: Rc<Self>) { self.cancel.cancel(); - // SAFETY: This drops the closure and the callback info associated with it. - // Any retained function pointers to the closure become dangling pointers. - // It is up to the user to know that it is safe to call the `close()` on the - // UnsafeCallback instance. - unsafe { - let info = Box::from_raw(self.info); - let isolate = info.isolate.as_mut().unwrap(); - let _ = v8::Global::from_raw(isolate, info.callback); - let _ = v8::Global::from_raw(isolate, info.context); - } } } struct CallbackInfo { - pub parameters: Vec<NativeType>, - pub result: NativeType, pub async_work_sender: mpsc::UnboundedSender<PendingFfiAsyncWork>, pub callback: NonNull<v8::Function>, pub context: NonNull<v8::Context>, - pub isolate: *mut v8::Isolate, + pub parameters: Vec<NativeType>, + pub result: NativeType, + pub thread_id: u32, pub waker: Option<Waker>, } @@ -122,8 +120,8 @@ unsafe extern "C" fn deno_ffi_callback( args: *const *const c_void, info: &CallbackInfo, ) { - LOCAL_ISOLATE_POINTER.with(|s| { - if ptr::eq(*s.borrow(), info.isolate) { + LOCAL_THREAD_ID.with(|s| { + if *s.borrow() == info.thread_id { // Own isolate thread, okay to call directly do_ffi_callback(cif, info, result, args); } else { @@ -155,9 +153,6 @@ unsafe fn do_ffi_callback( ) { let callback: NonNull<v8::Function> = info.callback; let context: NonNull<v8::Context> = info.context; - let isolate: *mut v8::Isolate = info.isolate; - let isolate = &mut *isolate; - let callback = v8::Global::from_raw(isolate, callback); let context = std::mem::transmute::< NonNull<v8::Context>, v8::Local<v8::Context>, @@ -174,7 +169,10 @@ unsafe fn do_ffi_callback( // refer the same `let bool_value`. let mut cb_scope = v8::CallbackScope::new(context); let scope = &mut v8::HandleScope::new(&mut cb_scope); - let func = callback.open(scope); + let func = std::mem::transmute::< + NonNull<v8::Function>, + v8::Local<v8::Function>, + >(callback); let result = result as *mut c_void; let vals: &[*const c_void] = std::slice::from_raw_parts(args, info.parameters.len()); @@ -267,7 +265,6 @@ unsafe fn do_ffi_callback( let recv = v8::undefined(scope); let call_result = func.call(scope, recv.into(), ¶ms); - std::mem::forget(callback); if call_result.is_none() { // JS function threw an exception. Set the return value to zero and return. @@ -555,13 +552,21 @@ where let v8_value = cb.v8_value; let cb = v8::Local::<v8::Function>::try_from(v8_value)?; - let isolate: *mut v8::Isolate = &mut *scope as &mut v8::Isolate; - LOCAL_ISOLATE_POINTER.with(|s| { - if s.borrow().is_null() { - s.replace(isolate); + let thread_id: u32 = LOCAL_THREAD_ID.with(|s| { + let value = *s.borrow(); + if value == 0 { + let res = THREAD_ID_COUNTER.fetch_add(1, atomic::Ordering::SeqCst); + s.replace(res); + res + } else { + value } }); + if thread_id == 0 { + panic!("Isolate ID counter overflowed u32"); + } + let async_work_sender = state.borrow_mut::<FfiState>().async_work_sender.clone(); let callback = v8::Global::new(scope, cb).into_raw(); @@ -569,12 +574,12 @@ where let context = v8::Global::new(scope, current_context).into_raw(); let info: *mut CallbackInfo = Box::leak(Box::new(CallbackInfo { - parameters: args.parameters.clone(), - result: args.result.clone(), async_work_sender, callback, context, - isolate, + parameters: args.parameters.clone(), + result: args.result.clone(), + thread_id, waker: None, })); let cif = Cif::new( @@ -607,3 +612,24 @@ where Ok(array_value.into()) } + +#[op(v8)] +pub fn op_ffi_unsafe_callback_close( + state: &mut OpState, + scope: &mut v8::HandleScope, + rid: ResourceId, +) -> Result<(), AnyError> { + // SAFETY: This drops the closure and the callback info associated with it. + // Any retained function pointers to the closure become dangling pointers. + // It is up to the user to know that it is safe to call the `close()` on the + // UnsafeCallback instance. + unsafe { + let callback_resource = + state.resource_table.take::<UnsafeCallbackResource>(rid)?; + let info = Box::from_raw(callback_resource.info); + let _ = v8::Global::from_raw(scope, info.callback); + let _ = v8::Global::from_raw(scope, info.context); + callback_resource.close(); + } + Ok(()) +} |