Browse Source

n-api: Context for custom async operations

- Add napi_async_context opaque pointer type.
   (If needed, we could later add APIs for getting the async IDs
   out of this context.)
 - Add napi_async_init() and napi_async_destroy() APIs.
 - Add async_context parameter to napi_make_callback().
 - Add code and checks to test_make_callback to validate async context
   APIs by checking async hooks are called with correct context.
 - Update API documentation.

PR-URL: https://github.com/nodejs/node/pull/15189
Fixes: https://github.com/nodejs/node/issues/13254
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
canary-base
Jason Ginchereau 8 years ago
committed by Michael Dawson
parent
commit
0c258bdc40
  1. 214
      doc/api/n-api.md
  2. 61
      src/node_api.cc
  3. 24
      src/node_api.h
  4. 1
      src/node_api_types.h
  5. 12
      test/addons-napi/test_make_callback/binding.cc
  6. 39
      test/addons-napi/test_make_callback/test.js
  7. 4
      test/addons-napi/test_make_callback_recurse/binding.cc

214
doc/api/n-api.md

@ -41,7 +41,8 @@ The documentation for N-API is structured as follows:
* [Working with JavaScript Properties][]
* [Working with JavaScript Functions][]
* [Object Wrap][]
* [Asynchronous Operations][]
* [Simple Asynchronous Operations][]
* [Custom Asynchronous Operations][]
* [Promises][]
* [Script Execution][]
@ -264,7 +265,7 @@ It is intended only for logging purposes.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status
napi_status
napi_get_last_error_info(napi_env env,
const napi_extended_error_info** result);
```
@ -515,8 +516,8 @@ This API returns a JavaScript RangeError with the text provided.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_get_and_clear_last_exception(napi_env env,
napi_value* result);
napi_status napi_get_and_clear_last_exception(napi_env env,
napi_value* result);
```
- `[in] env`: The environment that the API is invoked under.
@ -531,7 +532,7 @@ This API returns true if an exception is pending.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_is_exception_pending(napi_env env, bool* result);
napi_status napi_is_exception_pending(napi_env env, bool* result);
```
- `[in] env`: The environment that the API is invoked under.
@ -551,7 +552,7 @@ thrown to immediately terminate the process.
added: v8.2.0
-->
```C
NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, const char* message);
NAPI_NO_RETURN void napi_fatal_error(const char* location, const char* message);
```
- `[in] location`: Optional location at which the error occurred.
@ -718,10 +719,10 @@ reverse order from which they were created.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_escape_handle(napi_env env,
napi_escapable_handle_scope scope,
napi_value escapee,
napi_value* result);
napi_status napi_escape_handle(napi_env env,
napi_escapable_handle_scope scope,
napi_value escapee,
napi_value* result);
```
- `[in] env`: The environment that the API is invoked under.
@ -1478,10 +1479,10 @@ of the ECMAScript Language Specification.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env,
const char* str,
size_t length,
napi_value* result);
napi_status napi_create_string_latin1(napi_env env,
const char* str,
size_t length,
napi_value* result);
```
- `[in] env`: The environment that the API is invoked under.
@ -1811,11 +1812,11 @@ JavaScript Number
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env,
napi_value value,
char* buf,
size_t bufsize,
size_t* result)
napi_status napi_get_value_string_latin1(napi_env env,
napi_value value,
char* buf,
size_t bufsize,
size_t* result)
```
- `[in] env`: The environment that the API is invoked under.
@ -2790,8 +2791,8 @@ in as arguments to the function.
Returns `napi_ok` if the API succeeded.
This method allows a JavaScript function object to be called from a native
add-on. This is an primary mechanism of calling back *from* the add-on's
native code *into* JavaScript. For special cases like calling into JavaScript
add-on. This is the primary mechanism of calling back *from* the add-on's
native code *into* JavaScript. For the special case of calling into JavaScript
after an async operation, see [`napi_make_callback`][].
A sample use case might look as follows. Consider the following JavaScript
@ -3003,39 +3004,6 @@ status = napi_new_instance(env, constructor, argc, argv, &value);
Returns `napi_ok` if the API succeeded.
### *napi_make_callback*
<!-- YAML
added: v8.0.0
-->
```C
napi_status napi_make_callback(napi_env env,
napi_value recv,
napi_value func,
int argc,
const napi_value* argv,
napi_value* result)
```
- `[in] env`: The environment that the API is invoked under.
- `[in] recv`: The `this` object passed to the called function.
- `[in] func`: `napi_value` representing the JavaScript function
to be invoked.
- `[in] argc`: The count of elements in the `argv` array.
- `[in] argv`: Array of JavaScript values as `napi_value`
representing the arguments to the function.
- `[out] result`: `napi_value` representing the JavaScript object returned.
Returns `napi_ok` if the API succeeded.
This method allows a JavaScript function object to be called from a native
add-on. This API is similar to `napi_call_function`. However, it is used to call
*from* native code back *into* JavaScript *after* returning from an async
operation (when there is no other script on the stack). It is a fairly simple
wrapper around `node::MakeCallback`.
For an example on how to use `napi_make_callback`, see the section on
[Asynchronous Operations][].
## Object Wrap
N-API offers a way to "wrap" C++ classes and instances so that the class
@ -3214,7 +3182,7 @@ restoring the JavaScript object's prototype chain. If a finalize callback was
associated with the wrapping, it will no longer be called when the JavaScript
object becomes garbage-collected.
## Asynchronous Operations
## Simple Asynchronous Operations
Addon modules often need to leverage async helpers from libuv as part of their
implementation. This allows them to schedule work to be executed asynchronously
@ -3250,8 +3218,8 @@ Once created the async worker can be queued
for execution using the [`napi_queue_async_work`][] function:
```C
NAPI_EXTERN napi_status napi_queue_async_work(napi_env env,
napi_async_work work);
napi_status napi_queue_async_work(napi_env env,
napi_async_work work);
```
[`napi_cancel_async_work`][] can be used if the work needs
@ -3271,7 +3239,6 @@ changes:
description: Added `async_resource` and `async_resource_name` parameters.
-->
```C
NAPI_EXTERN
napi_status napi_create_async_work(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
@ -3314,8 +3281,8 @@ for more information.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_delete_async_work(napi_env env,
napi_async_work work);
napi_status napi_delete_async_work(napi_env env,
napi_async_work work);
```
- `[in] env`: The environment that the API is invoked under.
@ -3330,8 +3297,8 @@ This API frees a previously allocated work object.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_queue_async_work(napi_env env,
napi_async_work work);
napi_status napi_queue_async_work(napi_env env,
napi_async_work work);
```
- `[in] env`: The environment that the API is invoked under.
@ -3347,8 +3314,8 @@ for execution.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env,
napi_async_work work);
napi_status napi_cancel_async_work(napi_env env,
napi_async_work work);
```
- `[in] env`: The environment that the API is invoked under.
@ -3363,6 +3330,93 @@ the `complete` callback will be invoked with a status value of
`napi_cancelled`. The work should not be deleted before the `complete`
callback invocation, even if it has been successfully cancelled.
## Custom Asynchronous Operations
The simple asynchronous work APIs above may not be appropriate for every
scenario, because with those the async execution still happens on the main
event loop. When using any other async mechanism, the following APIs are
necessary to ensure an async operation is properly tracked by the runtime.
### *napi_async_init**
<!-- YAML
added: REPLACEME
-->
```C
napi_status napi_async_init(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_context* result)
```
- `[in] env`: The environment that the API is invoked under.
- `[in] async_resource`: An optional object associated with the async work
that will be passed to possible `async_hooks` [`init` hooks][].
- `[in] async_resource_name`: Required identifier for the kind of resource
that is being provided for diagnostic information exposed by the
`async_hooks` API.
- `[out] result`: The initialized async context.
Returns `napi_ok` if the API succeeded.
### *napi_async_destroy**
<!-- YAML
added: REPLACEME
-->
```C
napi_status napi_async_destroy(napi_env env,
napi_async_context async_context);
```
- `[in] env`: The environment that the API is invoked under.
- `[in] async_context`: The async context to be destroyed.
Returns `napi_ok` if the API succeeded.
### *napi_make_callback*
<!-- YAML
added: v8.0.0
changes:
- version: REPLACEME
description: Added `async_context` parameter.
-->
```C
napi_status napi_make_callback(napi_env env,
napi_async_context async_context,
napi_value recv,
napi_value func,
int argc,
const napi_value* argv,
napi_value* result)
```
- `[in] env`: The environment that the API is invoked under.
- `[in] async_context`: Context for the async operation that is
invoking the callback. This should normally be a value previously
obtained from [`napi_async_init`][]. However `NULL` is also allowed,
which indicates the current async context (if any) is to be used
for the callback.
- `[in] recv`: The `this` object passed to the called function.
- `[in] func`: `napi_value` representing the JavaScript function
to be invoked.
- `[in] argc`: The count of elements in the `argv` array.
- `[in] argv`: Array of JavaScript values as `napi_value`
representing the arguments to the function.
- `[out] result`: `napi_value` representing the JavaScript object returned.
Returns `napi_ok` if the API succeeded.
This method allows a JavaScript function object to be called from a native
add-on. This API is similar to `napi_call_function`. However, it is used to call
*from* native code back *into* JavaScript *after* returning from an async
operation (when there is no other script on the stack). It is a fairly simple
wrapper around `node::MakeCallback`.
Note it is *not* necessary to use `napi_make_callback` from within a
`napi_async_complete_callback`; in that situation the callback's async
context has already been set up, so a direct call to `napi_call_function`
is sufficient and appropriate. Use of the `napi_make_callback` function
may be required when implementing custom async behavior that does not use
`napi_create_async_work`.
## Version Management
### napi_get_node_version
@ -3378,7 +3432,6 @@ typedef struct {
const char* release;
} napi_node_version;
NAPI_EXTERN
napi_status napi_get_node_version(napi_env env,
const napi_node_version** version);
```
@ -3399,8 +3452,8 @@ The returned buffer is statically allocated and does not need to be freed.
added: v8.0.0
-->
```C
NAPI_EXTERN napi_status napi_get_version(napi_env env,
uint32_t* result);
napi_status napi_get_version(napi_env env,
uint32_t* result);
```
- `[in] env`: The environment that the API is invoked under.
@ -3508,9 +3561,9 @@ deferred = NULL;
added: v8.5.0
-->
```C
NAPI_EXTERN napi_status napi_create_promise(napi_env env,
napi_deferred* deferred,
napi_value* promise);
napi_status napi_create_promise(napi_env env,
napi_deferred* deferred,
napi_value* promise);
```
- `[in] env`: The environment that the API is invoked under.
@ -3528,9 +3581,9 @@ This API creates a deferred object and a JavaScript promise.
added: v8.5.0
-->
```C
NAPI_EXTERN napi_status napi_resolve_deferred(napi_env env,
napi_deferred deferred,
napi_value resolution);
napi_status napi_resolve_deferred(napi_env env,
napi_deferred deferred,
napi_value resolution);
```
- `[in] env`: The environment that the API is invoked under.
@ -3551,9 +3604,9 @@ The deferred object is freed upon successful completion.
added: v8.5.0
-->
```C
NAPI_EXTERN napi_status napi_reject_deferred(napi_env env,
napi_deferred deferred,
napi_value rejection);
napi_status napi_reject_deferred(napi_env env,
napi_deferred deferred,
napi_value rejection);
```
- `[in] env`: The environment that the API is invoked under.
@ -3574,9 +3627,9 @@ The deferred object is freed upon successful completion.
added: v8.5.0
-->
```C
NAPI_EXTERN napi_status napi_is_promise(napi_env env,
napi_value promise,
bool* is_promise);
napi_status napi_is_promise(napi_env env,
napi_value promise,
bool* is_promise);
```
- `[in] env`: The environment that the API is invoked under.
@ -3604,7 +3657,8 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env,
- `[out] result`: The value resulting from having executed the script.
[Promises]: #n_api_promises
[Asynchronous Operations]: #n_api_asynchronous_operations
[Simple Asynchronous Operations]: #n_api_asynchronous_operations
[Custom Asynchronous Operations]: #n_api_custom_asynchronous_operations
[Basic N-API Data Types]: #n_api_basic_n_api_data_types
[ECMAScript Language Specification]: https://tc39.github.io/ecma262/
[Error Handling]: #n_api_error_handling

61
src/node_api.cc

@ -2765,7 +2765,52 @@ napi_status napi_instanceof(napi_env env,
return GET_RETURN_STATUS(env);
}
napi_status napi_async_init(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_context* result) {
CHECK_ENV(env);
CHECK_ARG(env, async_resource_name);
CHECK_ARG(env, result);
v8::Isolate* isolate = env->isolate;
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> v8_resource;
if (async_resource != nullptr) {
CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
} else {
v8_resource = v8::Object::New(isolate);
}
v8::Local<v8::String> v8_resource_name;
CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
// TODO(jasongin): Consider avoiding allocation here by using
// a tagged pointer with 2×31 bit fields instead.
node::async_context* async_context = new node::async_context();
*async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name);
*result = reinterpret_cast<napi_async_context>(async_context);
return napi_clear_last_error(env);
}
napi_status napi_async_destroy(napi_env env,
napi_async_context async_context) {
CHECK_ENV(env);
CHECK_ARG(env, async_context);
v8::Isolate* isolate = env->isolate;
node::async_context* node_async_context =
reinterpret_cast<node::async_context*>(async_context);
node::EmitAsyncDestroy(isolate, *node_async_context);
return napi_clear_last_error(env);
}
napi_status napi_make_callback(napi_env env,
napi_async_context async_context,
napi_value recv,
napi_value func,
size_t argc,
@ -2786,12 +2831,22 @@ napi_status napi_make_callback(napi_env env,
v8::Local<v8::Function> v8func;
CHECK_TO_FUNCTION(env, v8func, func);
v8::Local<v8::Value> callback_result = node::MakeCallback(
node::async_context* node_async_context =
reinterpret_cast<node::async_context*>(async_context);
if (node_async_context == nullptr) {
static node::async_context empty_context = { 0, 0 };
node_async_context = &empty_context;
}
v8::MaybeLocal<v8::Value> callback_result = node::MakeCallback(
isolate, v8recv, v8func, argc,
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)));
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
*node_async_context);
CHECK_MAYBE_EMPTY(env, callback_result, napi_generic_failure);
if (result != nullptr) {
*result = v8impl::JsValueFromV8LocalValue(callback_result);
*result = v8impl::JsValueFromV8LocalValue(
callback_result.ToLocalChecked());
}
return GET_RETURN_STATUS(env);

24
src/node_api.h

@ -318,14 +318,6 @@ NAPI_EXTERN napi_status napi_instanceof(napi_env env,
napi_value constructor,
bool* result);
// Napi version of node::MakeCallback(...)
NAPI_EXTERN napi_status napi_make_callback(napi_env env,
napi_value recv,
napi_value func,
size_t argc,
const napi_value* argv,
napi_value* result);
// Methods to work with napi_callbacks
// Gets all callback info in a single call. (Ugly, but faster.)
@ -535,6 +527,22 @@ NAPI_EXTERN napi_status napi_queue_async_work(napi_env env,
NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env,
napi_async_work work);
// Methods for custom handling of async operations
NAPI_EXTERN napi_status napi_async_init(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_context* result);
NAPI_EXTERN napi_status napi_async_destroy(napi_env env,
napi_async_context async_context);
NAPI_EXTERN napi_status napi_make_callback(napi_env env,
napi_async_context async_context,
napi_value recv,
napi_value func,
size_t argc,
const napi_value* argv,
napi_value* result);
// version management
NAPI_EXTERN napi_status napi_get_version(napi_env env, uint32_t* result);

1
src/node_api_types.h

@ -16,6 +16,7 @@ typedef struct napi_ref__ *napi_ref;
typedef struct napi_handle_scope__ *napi_handle_scope;
typedef struct napi_escapable_handle_scope__ *napi_escapable_handle_scope;
typedef struct napi_callback_info__ *napi_callback_info;
typedef struct napi_async_context__ *napi_async_context;
typedef struct napi_async_work__ *napi_async_work;
typedef struct napi_deferred__ *napi_deferred;

12
test/addons-napi/test_make_callback/binding.cc

@ -24,14 +24,22 @@ napi_value MakeCallback(napi_env env, napi_callback_info info) {
NAPI_CALL(env, napi_typeof(env, func, &func_type));
napi_value resource_name;
NAPI_CALL(env, napi_create_string_utf8(env, "test", -1, &resource_name));
napi_async_context context;
NAPI_CALL(env, napi_async_init(env, func, resource_name, &context));
napi_value result;
if (func_type == napi_function) {
NAPI_CALL(env,
napi_make_callback(env, recv, func, argv.size(), argv.data(), &result));
NAPI_CALL(env, napi_make_callback(
env, context, recv, func, argv.size(), argv.data(), &result));
} else {
NAPI_ASSERT(env, false, "Unexpected argument type");
}
NAPI_CALL(env, napi_async_destroy(env, context));
return result;
}

39
test/addons-napi/test_make_callback/test.js

@ -2,12 +2,12 @@
const common = require('../../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
const vm = require('vm');
const binding = require(`./build/${common.buildType}/binding`);
const makeCallback = binding.makeCallback;
function myMultiArgFunc(arg1, arg2, arg3) {
console.log(`MyFunc was called with ${arguments.length} arguments`);
assert.strictEqual(arg1, 1);
assert.strictEqual(arg2, 2);
assert.strictEqual(arg3, 3);
@ -81,3 +81,40 @@ function endpoint($Object) {
}
assert.strictEqual(Object, makeCallback(process, forward, endpoint));
// Check async hooks integration using async context.
const hook_result = {
id: null,
init_called: false,
before_called: false,
after_called: false,
destroy_called: false,
};
const test_hook = async_hooks.createHook({
init: (id, type) => {
if (type === 'test') {
hook_result.id = id;
hook_result.init_called = true;
}
},
before: (id) => {
if (id === hook_result.id) hook_result.before_called = true;
},
after: (id) => {
if (id === hook_result.id) hook_result.after_called = true;
},
destroy: (id) => {
if (id === hook_result.id) hook_result.destroy_called = true;
},
});
test_hook.enable();
makeCallback(process, function() {});
assert.strictEqual(hook_result.init_called, true);
assert.strictEqual(hook_result.before_called, true);
assert.strictEqual(hook_result.after_called, true);
setImmediate(() => {
assert.strictEqual(hook_result.destroy_called, true);
test_hook.disable();
});

4
test/addons-napi/test_make_callback_recurse/binding.cc

@ -12,8 +12,8 @@ napi_value MakeCallback(napi_env env, napi_callback_info info) {
napi_value recv = args[0];
napi_value func = args[1];
napi_make_callback(env,
recv, func, 0 /* argc */, nullptr /* argv */, nullptr /* result */);
napi_make_callback(env, nullptr /* async_context */,
recv, func, 0 /* argc */, nullptr /* argv */, nullptr /* result */);
return recv;
}

Loading…
Cancel
Save