nodex_api/
tsfn.rs

1use crate::prelude::*;
2use std::marker::PhantomData;
3
4#[derive(Copy, Clone, Debug)]
5pub struct NapiThreadsafeFunction<Data, const N: usize>(
6    NapiEnv,
7    napi_threadsafe_function,
8    PhantomData<Data>,
9);
10
11unsafe impl<Data, const N: usize> Send for NapiThreadsafeFunction<Data, N> {}
12unsafe impl<Data, const N: usize> Sync for NapiThreadsafeFunction<Data, N> {}
13
14impl<Data, const N: usize> NapiThreadsafeFunction<Data, N> {
15    pub(crate) fn from_raw(env: NapiEnv, tsfn: napi_threadsafe_function) -> Self {
16        NapiThreadsafeFunction(env, tsfn, PhantomData)
17    }
18
19    pub fn env(&self) -> NapiEnv {
20        self.0
21    }
22
23    pub fn raw(&self) -> napi_threadsafe_function {
24        self.1
25    }
26
27    #[allow(clippy::type_complexity)]
28    /// Create a napi_threadsafe_function
29    ///
30    /// R: the returned value of function.
31    /// N: the maximum size of the queue, 0 for no limit.
32    pub fn new<R: NapiValueT>(
33        env: NapiEnv,
34        name: impl AsRef<str>,
35        func: Function<R>,
36        finalizer: impl FnOnce(NapiEnv) -> NapiResult<()>,
37        callback: impl FnMut(Function<R>, Data) -> NapiResult<()>,
38    ) -> NapiResult<NapiThreadsafeFunction<Data, N>>
39    where
40        R: NapiValueT,
41    {
42        unsafe extern "C" fn finalizer_trampoline(
43            env: NapiEnv,
44            finalizer: DataPointer,
45            _: DataPointer,
46        ) {
47            let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
48                Box::from_raw(finalizer as _);
49
50            if let Err(err) = finalizer(env) {
51                log::error!("NapiThreadsafeFunction::finalizer(): {}", err);
52            }
53        }
54
55        unsafe extern "C" fn call_js_trampoline<R: NapiValueT, Data>(
56            env: NapiEnv,
57            cb: napi_value,
58            context: DataPointer,
59            data: DataPointer,
60        ) {
61            let context: &mut Box<dyn FnMut(Function<R>, Data) -> NapiResult<()>> =
62                std::mem::transmute(&mut *(context as *mut _));
63            let data: Box<Data> = Box::from_raw(data as _);
64
65            if let Err(e) = context(Function::<R>::from_raw(env, cb), *data) {
66                log::error!("NapiThreadsafeFunction::call_js_trampoline: {}", e);
67            }
68        }
69
70        let context: Box<Box<dyn FnMut(Function<R>, Data) -> NapiResult<()>>> =
71            Box::new(Box::new(callback));
72        // NB: leak here
73        let context = Box::into_raw(context);
74        let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
75            Box::new(Box::new(move |env| -> NapiResult<()> {
76                unsafe {
77                    Box::from_raw(context);
78                }
79                finalizer(env)
80            }));
81
82        let tsfn = napi_call!(
83            =napi_create_threadsafe_function,
84            env,
85            func.raw(),
86            std::ptr::null_mut(),
87            env.string(name.as_ref())?.raw(),
88            N,
89            1,
90            Box::into_raw(finalizer) as _,
91            Some(finalizer_trampoline),
92            context as _,
93            Some(call_js_trampoline::<R, Data>),
94        );
95
96        Ok(NapiThreadsafeFunction(env, tsfn, PhantomData))
97    }
98
99    // pub fn context<C>(&self) -> NapiResult<&mut C> {
100    //     let context = napi_call!(=napi_get_threadsafe_function_context, self.raw());
101    //     unsafe { Ok(std::mem::transmute(context)) }
102    // }
103
104    /// This API should not be called with napi_tsfn_blocking from a JavaScript thread, because,
105    /// if the queue is full, it may cause the JavaScript thread to deadlock.
106    ///
107    /// This API will return napi_closing if napi_release_threadsafe_function() was called with
108    /// abort set to napi_tsfn_abort from any thread. The value is only added to the queue if
109    /// the API returns napi_ok.
110    ///
111    /// This API may be called from any thread which makes use of func.
112    pub fn call(&self, data: Data, mode: NapiThreadsafeFunctionCallMode) -> NapiResult<()> {
113        napi_call!(
114            napi_call_threadsafe_function,
115            self.raw(),
116            Box::into_raw(Box::new(data)) as _,
117            mode,
118        )
119    }
120
121    #[inline]
122    pub fn blocking(&self, data: Data) -> NapiResult<()> {
123        self.call(data, NapiThreadsafeFunctionCallMode::Blocking)
124    }
125
126    #[inline]
127    pub fn non_blocking(&self, data: Data) -> NapiResult<()> {
128        self.call(data, NapiThreadsafeFunctionCallMode::Nonblocking)
129    }
130
131    /// A thread should call this API before passing func to any other thread-safe function APIs
132    /// to indicate that it will be making use of func. This prevents func from being destroyed
133    /// when all other threads have stopped making use of it.
134    ///
135    /// This API may be called from any thread which will start making use of func.
136    pub fn acquire(&self) -> NapiResult<()> {
137        napi_call!(napi_acquire_threadsafe_function, self.raw())
138    }
139
140    /// A thread should call this API when it stops making use of func. Passing func to any
141    /// thread-safe APIs after having called this API has undefined results, as func may have
142    /// been destroyed.
143    ///
144    /// This API may be called from any thread which will stop making use of func.
145    pub fn release(self) -> NapiResult<()> {
146        napi_call!(
147            napi_release_threadsafe_function,
148            self.raw(),
149            NapiTsfnReleaseMode::Release
150        )
151    }
152
153    /// A thread should call this API when it stops making use of func. Passing func to any
154    /// thread-safe APIs after having called this API has undefined results, as func may have
155    /// been destroyed.
156    ///
157    /// This API may be called from any thread which will stop making use of func.
158    pub fn abort(self) -> NapiResult<()> {
159        napi_call!(
160            napi_release_threadsafe_function,
161            self.raw(),
162            NapiTsfnReleaseMode::Abort
163        )
164    }
165
166    /// This API is used to indicate that the event loop running on the main thread should not
167    /// exit until func has been destroyed. Similar to uv_ref it is also idempotent.
168    ///
169    /// Neither does napi_unref_threadsafe_function mark the thread-safe functions as able to be
170    /// destroyed nor does napi_ref_threadsafe_function prevent it from being destroyed.
171    /// napi_acquire_threadsafe_function and napi_release_threadsafe_function are available for
172    /// that purpose.
173    ///
174    /// This API may only be called from the main thread.
175    pub fn refer(&self) -> NapiResult<()> {
176        napi_call!(napi_ref_threadsafe_function, self.env(), self.raw())
177    }
178
179    /// This API is used to indicate that the event loop running on the main thread may exit
180    /// before func is destroyed. Similar to uv_unref it is also idempotent.
181    ///
182    /// This API may only be called from the main thread.
183    pub fn unref(&self) -> NapiResult<()> {
184        napi_call!(napi_unref_threadsafe_function, self.env(), self.raw())
185    }
186}
187
188pub type NapiTsfn<Data> = NapiThreadsafeFunction<Data, 0>;