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}