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 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 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 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}