Skip to main content

rspack_napi/
callback.rs

1use std::{
2  marker::PhantomData,
3  os::raw::c_void,
4  ptr,
5  sync::{
6    Arc,
7    atomic::{AtomicBool, AtomicPtr, Ordering},
8  },
9};
10
11use napi::{Env, Result, check_status, sys};
12
13struct DeferredData<Resolver: FnOnce(Env)> {
14  resolver: Resolver,
15}
16
17struct JsCallbackInfo {
18  tsfn: AtomicPtr<sys::napi_threadsafe_function__>,
19  aborted: AtomicBool,
20}
21
22impl Drop for JsCallbackInfo {
23  fn drop(&mut self) {
24    if self.aborted.load(Ordering::Relaxed) {
25      return;
26    }
27    let status = unsafe {
28      sys::napi_release_threadsafe_function(
29        self.tsfn.load(Ordering::Acquire),
30        sys::ThreadsafeFunctionReleaseMode::release,
31      )
32    };
33    debug_assert!(
34      status == sys::Status::napi_ok,
35      "Release ThreadsafeFunction in JsCallback failed"
36    );
37  }
38}
39
40pub struct JsCallback<Resolver: FnOnce(Env)> {
41  callback_info: Arc<JsCallbackInfo>,
42  _resolver: PhantomData<Resolver>,
43}
44
45unsafe impl<Resolver: FnOnce(Env)> Send for JsCallback<Resolver> {}
46unsafe impl<Resolver: FnOnce(Env)> Sync for JsCallback<Resolver> {}
47
48impl<Resolver: FnOnce(Env)> JsCallback<Resolver> {
49  /// # Safety
50  /// The provided `env` must be a valid `napi_env`.
51  pub unsafe fn new(env: sys::napi_env) -> Result<Self> {
52    let mut async_resource_name = ptr::null_mut();
53    let s = c"napi_js_callback";
54    check_status!(
55      unsafe { sys::napi_create_string_utf8(env, s.as_ptr(), 16, &mut async_resource_name) },
56      "Create async resource name in JsCallback failed"
57    )?;
58
59    let mut tsfn = ptr::null_mut();
60    let callback_info = Arc::new(JsCallbackInfo {
61      aborted: AtomicBool::new(false),
62      tsfn: AtomicPtr::new(ptr::null_mut()),
63    });
64    check_status!(
65      unsafe {
66        sys::napi_create_threadsafe_function(
67          env,
68          ptr::null_mut(),
69          ptr::null_mut(),
70          async_resource_name,
71          0,
72          1,
73          Arc::into_raw(callback_info.clone()) as _,
74          Some(napi_js_finalize_cb),
75          ptr::null_mut(),
76          Some(napi_js_callback::<Resolver>),
77          &mut tsfn,
78        )
79      },
80      "Create threadsafe function in JsCallback failed"
81    )?;
82    callback_info.tsfn.store(tsfn, Ordering::Release);
83    check_status!(unsafe { sys::napi_unref_threadsafe_function(env, tsfn) })?;
84
85    let deferred = Self {
86      callback_info,
87      _resolver: PhantomData,
88    };
89
90    Ok(deferred)
91  }
92
93  /// The provided function will be called from the JavaScript thread
94  pub fn call(&self, resolver: Resolver) {
95    self.call_tsfn(resolver)
96  }
97
98  fn call_tsfn(&self, result: Resolver) {
99    let data = DeferredData { resolver: result };
100
101    // Call back into the JS thread via a threadsafe function. This results in napi_js_callback being called.
102    let status = unsafe {
103      sys::napi_call_threadsafe_function(
104        self.callback_info.tsfn.load(Ordering::Acquire),
105        Box::into_raw(Box::from(data)).cast(),
106        sys::ThreadsafeFunctionCallMode::blocking,
107      )
108    };
109    debug_assert!(
110      status == sys::Status::napi_ok,
111      "Call threadsafe function in JsCallback failed"
112    );
113  }
114}
115
116impl<Resolver: FnOnce(Env)> Clone for JsCallback<Resolver> {
117  fn clone(&self) -> Self {
118    if self.callback_info.aborted.load(Ordering::Relaxed) {
119      panic!("JsCallback was aborted, can not clone it");
120    }
121    Self {
122      callback_info: self.callback_info.clone(),
123      _resolver: PhantomData,
124    }
125  }
126}
127
128extern "C" fn napi_js_finalize_cb(
129  _env: sys::napi_env,
130  finalize_data: *mut c_void,
131  _finalize_hint: *mut c_void,
132) {
133  let callback_info = unsafe { Arc::<JsCallbackInfo>::from_raw(finalize_data.cast()) };
134  callback_info.aborted.store(true, Ordering::Relaxed);
135}
136
137extern "C" fn napi_js_callback<Resolver: FnOnce(Env)>(
138  env: sys::napi_env,
139  _js_callback: sys::napi_value,
140  _context: *mut c_void,
141  data: *mut c_void,
142) {
143  if env.is_null() {
144    return;
145  }
146  let deferred_data = unsafe { Box::<DeferredData<Resolver>>::from_raw(data.cast()) };
147  (deferred_data.resolver)(Env::from_raw(env));
148}