-
Notifications
You must be signed in to change notification settings - Fork 284
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(neon): Implementation for JsPromise and TaskBuilder
- Loading branch information
1 parent
cb884ee
commit e4f75dc
Showing
24 changed files
with
898 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
[alias] | ||
# Neon defines mutually exclusive feature flags which prevents using `cargo clippy --all-features` | ||
# The following aliases simplify linting the entire workspace | ||
check-napi = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental" | ||
check-napi = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental,promise-api,task-api" | ||
check-legacy = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p tests -p static_tests --features event-handler-api,proc-macros,try-catch-api,legacy-runtime" | ||
clippy-legacy = "clippy --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p tests -p static_tests --features event-handler-api,proc-macros,try-catch-api,legacy-runtime -- -A clippy::missing_safety_doc" | ||
clippy-napi = "clippy --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental -- -A clippy::missing_safety_doc" | ||
clippy-napi = "clippy --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental,promise-api,task-api -- -A clippy::missing_safety_doc" | ||
neon-test = "test --no-default-features --features napi-experimental" | ||
neon-doc = "rustdoc --no-default-features --features=channel-api,napi-experimental,proc-macros,try-catch-api -- --cfg docsrs" | ||
neon-doc = "rustdoc --no-default-features --features=channel-api,napi-experimental,proc-macros,try-catch-api,promise-api,task-api -- --cfg docsrs" | ||
neon-doc-test = "test --doc --no-default-features --features=channel-api,napi-experimental,proc-macros,try-catch-api,promise-api,task-api" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
//! Rust wrappers for Node-API simple asynchronous operations | ||
//! | ||
//! Unlike `napi_async_work` which threads a single mutable pointer to a data | ||
//! struct to both the `execute` and `complete` callbacks, the wrapper follows | ||
//! a more idiomatic Rust ownership pattern by passing the output of `execute` | ||
//! into the input of `complete`. | ||
//! | ||
//! https://nodejs.org/api/n-api.html#n_api_simple_asynchronous_operations | ||
|
||
use std::ffi::c_void; | ||
use std::mem; | ||
use std::ptr; | ||
|
||
use crate::napi::bindings as napi; | ||
use crate::raw::Env; | ||
|
||
type Execute<T, O> = fn(input: T) -> O; | ||
type Complete<O> = fn(env: Env, output: O); | ||
|
||
/// Schedule work to execute on the libuv thread pool | ||
/// | ||
/// # Safety | ||
/// * `env` must be a valid `napi_env` for the current thread | ||
pub unsafe fn schedule<T, O>(env: Env, input: T, execute: Execute<T, O>, complete: Complete<O>) | ||
where | ||
T: Send + 'static, | ||
O: Send + 'static, | ||
{ | ||
let mut data = Box::new(Data { | ||
state: State::Input(input), | ||
execute, | ||
complete, | ||
// Work is initialized as a null pointer, but set by `create_async_work` | ||
// `data` must not be used until this value has been set. | ||
work: ptr::null_mut(), | ||
}); | ||
|
||
// Store a pointer to `work` before ownership is transferred to `Box::into_raw` | ||
let work = &mut data.work as *mut _; | ||
|
||
// Create the `async_work` | ||
assert_eq!( | ||
napi::create_async_work( | ||
env, | ||
ptr::null_mut(), | ||
super::string(env, "neon_async_work"), | ||
Some(call_execute::<T, O>), | ||
Some(call_complete::<T, O>), | ||
Box::into_raw(data).cast(), | ||
work, | ||
), | ||
napi::Status::Ok, | ||
); | ||
|
||
// Queue the work | ||
match napi::queue_async_work(env, *work) { | ||
napi::Status::Ok => {} | ||
status => { | ||
// If queueing failed, delete the work to prevent a leak | ||
napi::delete_async_work(env, *work); | ||
assert_eq!(status, napi::Status::Ok); | ||
} | ||
} | ||
} | ||
|
||
/// A pointer to data is passed to the `execute` and `complete` callbacks | ||
struct Data<T, O> { | ||
state: State<T, O>, | ||
execute: Execute<T, O>, | ||
complete: Complete<O>, | ||
work: napi::AsyncWork, | ||
} | ||
|
||
/// State of the task that is transitioned by `execute` and `complete` | ||
enum State<T, O> { | ||
/// Initial data input passed to `execute` | ||
Input(T), | ||
/// Transient state while `execute` is running | ||
Executing, | ||
/// Return data of `execute` passed to `complete` | ||
Output(O), | ||
} | ||
|
||
impl<T, O> State<T, O> { | ||
/// Return the input if `State::Input`, replacing with `State::Executing` | ||
fn take_execute_input(&mut self) -> Option<T> { | ||
match mem::replace(self, Self::Executing) { | ||
Self::Input(input) => Some(input), | ||
_ => None, | ||
} | ||
} | ||
|
||
/// Return the output if `State::Output`, replacing with `State::Executing` | ||
fn into_output(self) -> Option<O> { | ||
match self { | ||
Self::Output(output) => Some(output), | ||
_ => None, | ||
} | ||
} | ||
} | ||
|
||
/// Callback executed on the libuv thread pool | ||
/// | ||
/// # Safety | ||
/// * `Env` should not be used because it could attempt to call JavaScript | ||
/// * `data` is expected to be a pointer to `Data<T, O>` | ||
unsafe extern "C" fn call_execute<T, O>(_: Env, data: *mut c_void) { | ||
let data = &mut *data.cast::<Data<T, O>>(); | ||
// `unwrap` is ok because `call_execute` should be called exactly once | ||
// after initialization | ||
let input = data.state.take_execute_input().unwrap(); | ||
let output = (data.execute)(input); | ||
|
||
data.state = State::Output(output); | ||
} | ||
|
||
/// Callback executed on the JavaScript main thread | ||
/// | ||
/// # Safety | ||
/// * `data` is expected to be a pointer to `Data<T, O>` | ||
unsafe extern "C" fn call_complete<T, O>(env: Env, status: napi::Status, data: *mut c_void) { | ||
let Data { | ||
state, | ||
complete, | ||
work, | ||
.. | ||
} = *Box::<Data<T, O>>::from_raw(data.cast()); | ||
|
||
napi::delete_async_work(env, work); | ||
|
||
match status { | ||
// `unwrap` is okay because `call_complete` should be called exactly once | ||
// if and only if `call_execute` has completed successfully | ||
napi::Status::Ok => complete(env, state.into_output().unwrap()), | ||
napi::Status::Cancelled => {} | ||
_ => assert_eq!(status, napi::Status::Ok), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
//! JavaScript Promise and Deferred handle | ||
//! | ||
//! https://nodejs.org/api/n-api.html#n_api_promises | ||
|
||
use std::mem::MaybeUninit; | ||
use std::ptr; | ||
|
||
use crate::napi::bindings as napi; | ||
use crate::raw::Env; | ||
|
||
/// Create a `Promise` and a `napi::Deferred` handle for resolving it | ||
/// | ||
/// # Safety | ||
/// * `env` is a valid `napi_env` for the current thread | ||
/// * The returned `napi::Value` does not outlive `env` | ||
pub unsafe fn create(env: Env) -> (napi::Deferred, napi::Value) { | ||
let mut deferred = MaybeUninit::uninit(); | ||
let mut promise = MaybeUninit::uninit(); | ||
|
||
assert_eq!( | ||
napi::create_promise(env, deferred.as_mut_ptr(), promise.as_mut_ptr()), | ||
napi::Status::Ok, | ||
); | ||
|
||
(deferred.assume_init(), promise.assume_init()) | ||
} | ||
|
||
/// Resolve a promise from a `napi::Deferred` handle | ||
/// | ||
/// # Safety | ||
/// * `env` is a valid `napi_env` for the current thread | ||
/// * `resolution` is a valid `napi::Value` | ||
pub unsafe fn resolve(env: Env, deferred: napi::Deferred, resolution: napi::Value) { | ||
assert_eq!( | ||
napi::resolve_deferred(env, deferred, resolution), | ||
napi::Status::Ok, | ||
); | ||
} | ||
|
||
/// Rejects a promise from a `napi::Deferred` handle | ||
/// | ||
/// # Safety | ||
/// * `env` is a valid `napi_env` for the current thread | ||
/// * `rejection` is a valid `napi::Value` | ||
pub unsafe fn reject(env: Env, deferred: napi::Deferred, rejection: napi::Value) { | ||
assert_eq!( | ||
napi::reject_deferred(env, deferred, rejection), | ||
napi::Status::Ok, | ||
); | ||
} | ||
|
||
/// Rejects a promise from a `napi::Deferred` handle with a string message | ||
/// | ||
/// # Safety | ||
/// * `env` is a valid `napi_env` for the current thread | ||
pub unsafe fn reject_err_message(env: Env, deferred: napi::Deferred, msg: impl AsRef<str>) { | ||
let msg = super::string(env, msg); | ||
let mut err = MaybeUninit::uninit(); | ||
|
||
assert_eq!( | ||
napi::create_error(env, ptr::null_mut(), msg, err.as_mut_ptr()), | ||
napi::Status::Ok, | ||
); | ||
|
||
reject(env, deferred, err.assume_init()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.