mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
n-api: implement async helper methods
Based on the async methods we had in abi-stable-node before the napi feature landed in node/master. Changed this set of APIs to handle error cases and removed a lot of the extra methods we had for setting all the pieces of napi_work opting instead to pass all of those as arguments to napi_create_async_work as none of those parameters are optional except for the complete callback, anyway. Renamed the napi_work struct to napi_async_work and replace the struct itself with a class which can better encapsulate the object lifetime and uv_work_t that we're trying to wrap anyway. Added a napi_async_callback type for the async helper callbacks instead of taking raw function pointers and make this callback take a napi_env parameter as well as the void* data it was already taking. Call the complete handler for the async work item with a napi_status code translated from the uvlib error code. The execute callback is required for napi_create_async_work, though complete callback is still optional. Also added some async unit tests for addons-napi based on the addons/async_hello_world test. PR-URL: https://github.com/nodejs/node/pull/12250 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Hitesh Kanwathirtha <hiteshk@microsoft.com>
This commit is contained in:
committed by
Anna Henningsen
parent
ca786c3734
commit
9decfb1521
137
src/node_api.cc
137
src/node_api.cc
@@ -15,6 +15,7 @@
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include "node_api.h"
|
||||
#include "env-inl.h"
|
||||
|
||||
napi_status napi_set_last_error(napi_env env, napi_status error_code,
|
||||
uint32_t engine_error_code = 0,
|
||||
@@ -705,7 +706,8 @@ const char* error_messages[] = {nullptr,
|
||||
"A boolean was expected",
|
||||
"An array was expected",
|
||||
"Unknown failure",
|
||||
"An exception is pending"};
|
||||
"An exception is pending",
|
||||
"The async work item was cancelled"};
|
||||
|
||||
void napi_clear_last_error(napi_env env) {
|
||||
env->last_error.error_code = napi_ok;
|
||||
@@ -2574,3 +2576,136 @@ napi_status napi_get_typedarray_info(napi_env env,
|
||||
|
||||
return GET_RETURN_STATUS(env);
|
||||
}
|
||||
|
||||
namespace uvimpl {
|
||||
|
||||
napi_status ConvertUVErrorCode(int code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return napi_ok;
|
||||
case UV_EINVAL:
|
||||
return napi_invalid_arg;
|
||||
case UV_ECANCELED:
|
||||
return napi_cancelled;
|
||||
}
|
||||
|
||||
return napi_generic_failure;
|
||||
}
|
||||
|
||||
// Wrapper around uv_work_t which calls user-provided callbacks.
|
||||
class Work {
|
||||
private:
|
||||
explicit Work(napi_env env,
|
||||
napi_async_execute_callback execute = nullptr,
|
||||
napi_async_complete_callback complete = nullptr,
|
||||
void* data = nullptr)
|
||||
: _env(env),
|
||||
_data(data),
|
||||
_execute(execute),
|
||||
_complete(complete) {
|
||||
_request.data = this;
|
||||
}
|
||||
|
||||
~Work() { }
|
||||
|
||||
public:
|
||||
static Work* New(napi_env env,
|
||||
napi_async_execute_callback execute,
|
||||
napi_async_complete_callback complete,
|
||||
void* data) {
|
||||
return new Work(env, execute, complete, data);
|
||||
}
|
||||
|
||||
static void Delete(Work* work) {
|
||||
delete work;
|
||||
}
|
||||
|
||||
static void ExecuteCallback(uv_work_t* req) {
|
||||
Work* work = static_cast<Work*>(req->data);
|
||||
work->_execute(work->_env, work->_data);
|
||||
}
|
||||
|
||||
static void CompleteCallback(uv_work_t* req, int status) {
|
||||
Work* work = static_cast<Work*>(req->data);
|
||||
|
||||
if (work->_complete != nullptr) {
|
||||
work->_complete(work->_env, ConvertUVErrorCode(status), work->_data);
|
||||
}
|
||||
}
|
||||
|
||||
uv_work_t* Request() {
|
||||
return &_request;
|
||||
}
|
||||
|
||||
private:
|
||||
napi_env _env;
|
||||
void* _data;
|
||||
uv_work_t _request;
|
||||
napi_async_execute_callback _execute;
|
||||
napi_async_complete_callback _complete;
|
||||
};
|
||||
|
||||
} // end of namespace uvimpl
|
||||
|
||||
#define CALL_UV(env, condition) \
|
||||
do { \
|
||||
int result = (condition); \
|
||||
napi_status status = uvimpl::ConvertUVErrorCode(result); \
|
||||
if (status != napi_ok) { \
|
||||
return napi_set_last_error(env, status, result); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
napi_status napi_create_async_work(napi_env env,
|
||||
napi_async_execute_callback execute,
|
||||
napi_async_complete_callback complete,
|
||||
void* data,
|
||||
napi_async_work* result) {
|
||||
CHECK_ENV(env);
|
||||
CHECK_ARG(env, execute);
|
||||
CHECK_ARG(env, result);
|
||||
|
||||
uvimpl::Work* work = uvimpl::Work::New(env, execute, complete, data);
|
||||
|
||||
*result = reinterpret_cast<napi_async_work>(work);
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
napi_status napi_delete_async_work(napi_env env, napi_async_work work) {
|
||||
CHECK_ENV(env);
|
||||
CHECK_ARG(env, work);
|
||||
|
||||
uvimpl::Work::Delete(reinterpret_cast<uvimpl::Work*>(work));
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
napi_status napi_queue_async_work(napi_env env, napi_async_work work) {
|
||||
CHECK_ENV(env);
|
||||
CHECK_ARG(env, work);
|
||||
|
||||
// Consider: Encapsulate the uv_loop_t into an opaque pointer parameter
|
||||
uv_loop_t* event_loop =
|
||||
node::Environment::GetCurrent(env->isolate)->event_loop();
|
||||
|
||||
uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
|
||||
|
||||
CALL_UV(env, uv_queue_work(event_loop,
|
||||
w->Request(),
|
||||
uvimpl::Work::ExecuteCallback,
|
||||
uvimpl::Work::CompleteCallback));
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
napi_status napi_cancel_async_work(napi_env env, napi_async_work work) {
|
||||
CHECK_ENV(env);
|
||||
CHECK_ARG(env, work);
|
||||
|
||||
uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
|
||||
|
||||
CALL_UV(env, uv_cancel(reinterpret_cast<uv_req_t*>(w->Request())));
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
@@ -457,6 +457,21 @@ NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env,
|
||||
void** data,
|
||||
napi_value* arraybuffer,
|
||||
size_t* byte_offset);
|
||||
|
||||
// Methods to manage simple async operations
|
||||
NAPI_EXTERN
|
||||
napi_status napi_create_async_work(napi_env env,
|
||||
napi_async_execute_callback execute,
|
||||
napi_async_complete_callback complete,
|
||||
void* data,
|
||||
napi_async_work* result);
|
||||
NAPI_EXTERN napi_status napi_delete_async_work(napi_env env,
|
||||
napi_async_work work);
|
||||
NAPI_EXTERN napi_status napi_queue_async_work(napi_env env,
|
||||
napi_async_work work);
|
||||
NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env,
|
||||
napi_async_work work);
|
||||
|
||||
EXTERN_C_END
|
||||
|
||||
#endif // SRC_NODE_API_H__
|
||||
|
||||
@@ -16,12 +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 napi_value (*napi_callback)(napi_env env,
|
||||
napi_callback_info info);
|
||||
typedef void (*napi_finalize)(napi_env env,
|
||||
void* finalize_data,
|
||||
void* finalize_hint);
|
||||
typedef struct napi_async_work__ *napi_async_work;
|
||||
|
||||
typedef enum {
|
||||
napi_default = 0,
|
||||
@@ -34,20 +29,6 @@ typedef enum {
|
||||
napi_static = 1 << 10,
|
||||
} napi_property_attributes;
|
||||
|
||||
typedef struct {
|
||||
// One of utf8name or name should be NULL.
|
||||
const char* utf8name;
|
||||
napi_value name;
|
||||
|
||||
napi_callback method;
|
||||
napi_callback getter;
|
||||
napi_callback setter;
|
||||
napi_value value;
|
||||
|
||||
napi_property_attributes attributes;
|
||||
void* data;
|
||||
} napi_property_descriptor;
|
||||
|
||||
typedef enum {
|
||||
// ES6 types (corresponds to typeof)
|
||||
napi_undefined,
|
||||
@@ -85,9 +66,35 @@ typedef enum {
|
||||
napi_array_expected,
|
||||
napi_generic_failure,
|
||||
napi_pending_exception,
|
||||
napi_cancelled,
|
||||
napi_status_last
|
||||
} napi_status;
|
||||
|
||||
typedef napi_value (*napi_callback)(napi_env env,
|
||||
napi_callback_info info);
|
||||
typedef void (*napi_finalize)(napi_env env,
|
||||
void* finalize_data,
|
||||
void* finalize_hint);
|
||||
typedef void (*napi_async_execute_callback)(napi_env env,
|
||||
void* data);
|
||||
typedef void (*napi_async_complete_callback)(napi_env env,
|
||||
napi_status status,
|
||||
void* data);
|
||||
|
||||
typedef struct {
|
||||
// One of utf8name or name should be NULL.
|
||||
const char* utf8name;
|
||||
napi_value name;
|
||||
|
||||
napi_callback method;
|
||||
napi_callback getter;
|
||||
napi_callback setter;
|
||||
napi_value value;
|
||||
|
||||
napi_property_attributes attributes;
|
||||
void* data;
|
||||
} napi_property_descriptor;
|
||||
|
||||
typedef struct {
|
||||
const char* error_message;
|
||||
void* engine_reserved;
|
||||
|
||||
8
test/addons-napi/test_async/binding.gyp
Normal file
8
test/addons-napi/test_async/binding.gyp
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_async",
|
||||
"sources": [ "test_async.cc" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
10
test/addons-napi/test_async/test.js
Normal file
10
test/addons-napi/test_async/test.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const test_async = require(`./build/${common.buildType}/test_async`);
|
||||
|
||||
test_async(5, common.mustCall(function(err, val) {
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(val, 10);
|
||||
process.nextTick(common.mustCall(function() {}));
|
||||
}));
|
||||
122
test/addons-napi/test_async/test_async.cc
Normal file
122
test/addons-napi/test_async/test_async.cc
Normal file
@@ -0,0 +1,122 @@
|
||||
#include <node_api.h>
|
||||
#include "../common.h"
|
||||
|
||||
#if defined _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int32_t _input;
|
||||
int32_t _output;
|
||||
napi_ref _callback;
|
||||
napi_async_work _request;
|
||||
} carrier;
|
||||
|
||||
carrier the_carrier;
|
||||
|
||||
struct AutoHandleScope {
|
||||
explicit AutoHandleScope(napi_env env)
|
||||
: _env(env),
|
||||
_scope(nullptr) {
|
||||
napi_open_handle_scope(_env, &_scope);
|
||||
}
|
||||
~AutoHandleScope() {
|
||||
napi_close_handle_scope(_env, _scope);
|
||||
}
|
||||
private:
|
||||
AutoHandleScope() { }
|
||||
|
||||
napi_env _env;
|
||||
napi_handle_scope _scope;
|
||||
};
|
||||
|
||||
void Execute(napi_env env, void* data) {
|
||||
#if defined _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
carrier* c = static_cast<carrier*>(data);
|
||||
|
||||
if (c != &the_carrier) {
|
||||
napi_throw_type_error(env, "Wrong data parameter to Execute.");
|
||||
return;
|
||||
}
|
||||
|
||||
c->_output = c->_input * 2;
|
||||
}
|
||||
|
||||
void Complete(napi_env env, napi_status status, void* data) {
|
||||
AutoHandleScope scope(env);
|
||||
carrier* c = static_cast<carrier*>(data);
|
||||
|
||||
if (c != &the_carrier) {
|
||||
napi_throw_type_error(env, "Wrong data parameter to Complete.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != napi_ok) {
|
||||
napi_throw_type_error(env, "Execute callback failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
napi_value argv[2];
|
||||
|
||||
NAPI_CALL_RETURN_VOID(env, napi_get_null(env, &argv[0]));
|
||||
NAPI_CALL_RETURN_VOID(env, napi_create_number(env, c->_output, &argv[1]));
|
||||
napi_value callback;
|
||||
NAPI_CALL_RETURN_VOID(env,
|
||||
napi_get_reference_value(env, c->_callback, &callback));
|
||||
napi_value global;
|
||||
NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &global));
|
||||
|
||||
napi_value result;
|
||||
NAPI_CALL_RETURN_VOID(env,
|
||||
napi_call_function(env, global, callback, 2, argv, &result));
|
||||
|
||||
NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback));
|
||||
NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
|
||||
}
|
||||
|
||||
napi_value Test(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 2;
|
||||
napi_value argv[2];
|
||||
napi_value _this;
|
||||
void* data;
|
||||
NAPI_CALL(env,
|
||||
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
|
||||
NAPI_ASSERT(env, argc >= 2, "Not enough arguments, expected 2.");
|
||||
|
||||
napi_valuetype t;
|
||||
NAPI_CALL(env, napi_typeof(env, argv[0], &t));
|
||||
NAPI_ASSERT(env, t == napi_number,
|
||||
"Wrong first argument, integer expected.");
|
||||
NAPI_CALL(env, napi_typeof(env, argv[1], &t));
|
||||
NAPI_ASSERT(env, t == napi_function,
|
||||
"Wrong second argument, function expected.");
|
||||
|
||||
the_carrier._output = 0;
|
||||
|
||||
NAPI_CALL(env,
|
||||
napi_get_value_int32(env, argv[0], &the_carrier._input));
|
||||
NAPI_CALL(env,
|
||||
napi_create_reference(env, argv[1], 1, &the_carrier._callback));
|
||||
NAPI_CALL(env, napi_create_async_work(
|
||||
env, Execute, Complete, &the_carrier, &the_carrier._request));
|
||||
NAPI_CALL(env,
|
||||
napi_queue_async_work(env, the_carrier._request));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
|
||||
napi_value test;
|
||||
NAPI_CALL_RETURN_VOID(env,
|
||||
napi_create_function(env, "Test", Test, nullptr, &test));
|
||||
NAPI_CALL_RETURN_VOID(env,
|
||||
napi_set_named_property(env, module, "exports", test));
|
||||
}
|
||||
|
||||
NAPI_MODULE(addon, Init)
|
||||
Reference in New Issue
Block a user