nodex_api/
work.rs

1use crate::{api, prelude::*};
2use std::marker::PhantomData;
3
4#[derive(Debug)]
5pub struct NapiAsyncWork<T>(NapiEnv, napi_async_work, bool, PhantomData<T>);
6
7impl<T> NapiAsyncWork<T> {
8    pub(crate) fn from_raw(env: NapiEnv, work: napi_async_work) -> NapiAsyncWork<T> {
9        NapiAsyncWork(env, work, false, PhantomData)
10    }
11
12    pub fn env(&self) -> NapiEnv {
13        self.0
14    }
15
16    pub fn raw(&self) -> napi_async_work {
17        self.1
18    }
19
20    /// This API allocates a work object that is used to execute logic asynchronously.
21    /// It should be freed using napi_delete_async_work once the work is no longer required.
22    /// async_resource_name should be a null-terminated, UTF-8-encoded string.
23    ///
24    /// The async_resource_name identifier is provided by the user and should be representative
25    /// of the type of async work being performed. It is also recommended to apply namespacing
26    /// to the identifier, e.g. by including the module name. See the async_hooks documentation
27    /// for more information.
28    ///
29    /// # Arguments
30    ///
31    /// * `env` - napi_env
32    /// * `name` - napi async work identifier
33    /// * `state` - The state shared between `execute` & `complete`
34    /// * `execute` - The native function which should be called to execute the logic asynchronously. The given function is called from a worker pool thread and can execute in parallel with the main event loop thread.
35    /// * `complete` - The native function which will be called when the asynchronous logic is completed or is cancelled. The given function is called from the main event loop thread.
36    #[allow(clippy::type_complexity)]
37    pub fn new(
38        env: NapiEnv,
39        name: impl AsRef<str>,
40        state: T,
41        execute: impl FnMut(&mut T) + Send + 'static,
42        complete: impl FnMut(NapiEnv, NapiStatus, T) -> NapiResult<()> + 'static,
43    ) -> NapiResult<NapiAsyncWork<T>> {
44        extern "C" fn napi_async_execute_callback<T>(env: NapiEnv, data: DataPointer) {
45            unsafe {
46                // NB: We just access the execute function here, so read complete function as
47                // `dyn FnMut<&mut T>`. It only runs once.
48                let (execute, _, state): &mut (
49                    Box<dyn FnMut(&mut T)>,
50                    Box<dyn FnMut(NapiEnv, NapiStatus, T) -> NapiResult<()>>,
51                    T,
52                ) = std::mem::transmute(&mut *(data as *mut _));
53                execute(state);
54            }
55        }
56        extern "C" fn napi_async_complete_callback<T>(
57            env: NapiEnv,
58            status: NapiStatus,
59            data: DataPointer,
60        ) {
61            unsafe {
62                let mut pair: Box<(
63                    Box<dyn FnMut(&mut T)>,
64                    Box<dyn FnMut(NapiEnv, NapiStatus, T)>,
65                    T,
66                )> = Box::from_raw(data as _);
67                let mut complete = pair.1;
68                complete(env, status, pair.2);
69            }
70        }
71
72        let pair: Box<(
73            Box<dyn FnMut(&mut T)>,
74            Box<dyn FnMut(NapiEnv, NapiStatus, T) -> NapiResult<()>>,
75            T,
76        )> = Box::new((Box::new(execute), Box::new(complete), state));
77
78        let work = napi_call!(
79            =napi_create_async_work,
80            env,
81            env.object()?.raw(),
82            env.string(name)?.raw(),
83            Some(napi_async_execute_callback::<T>),
84            Some(napi_async_complete_callback::<T>),
85            Box::into_raw(pair) as _,
86        );
87
88        Ok(NapiAsyncWork::from_raw(env, work))
89    }
90
91    /// This API requests that the previously allocated work be scheduled for execution. Once it
92    /// returns successfully, this API must not be called again with the same napi_async_work item
93    /// or the result will be undefined.
94    ///
95    /// NB: The `NapiAsyncWork` can not be queued more than once.
96    pub fn queue(&mut self) -> NapiResult<Option<()>> {
97        if self.2 {
98            Ok(None)
99        } else {
100            napi_call!(napi_queue_async_work, self.env(), self.raw());
101            self.2 = true;
102            Ok(Some(()))
103        }
104    }
105
106    /// This API cancels queued work if it has not yet been started. If it has already
107    /// started executing, it cannot be cancelled and napi_generic_failure will be returned.
108    /// If successful, the complete callback will be invoked with a status value of
109    /// napi_cancelled. The work should not be deleted before the complete callback invocation,
110    /// even if it has been successfully cancelled.
111    ///
112    /// This API can be called even if there is a pending JavaScript exception.
113    pub fn cancel(&self) -> NapiResult<()> {
114        napi_call!(napi_cancel_async_work, self.env(), self.raw())
115    }
116
117    /// This API frees a previously allocated work object.
118    /// This API can be called even if there is a pending JavaScript exception.
119    ///
120    /// NB: should not delete a queued task.
121    pub fn delete(self) -> NapiResult<()> {
122        if !self.2 {
123            napi_call!(napi_delete_async_work, self.env(), self.raw());
124        }
125        Ok(())
126    }
127}