Browse Source

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>
v6
taylor.woll 8 years ago
committed by Anna Henningsen
parent
commit
9decfb1521
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  1. 137
      src/node_api.cc
  2. 15
      src/node_api.h
  3. 47
      src/node_api_types.h
  4. 8
      test/addons-napi/test_async/binding.gyp
  5. 10
      test/addons-napi/test_async/test.js
  6. 122
      test/addons-napi/test_async/test_async.cc

137
src/node_api.cc

@ -15,6 +15,7 @@
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include "node_api.h" #include "node_api.h"
#include "env-inl.h"
napi_status napi_set_last_error(napi_env env, napi_status error_code, napi_status napi_set_last_error(napi_env env, napi_status error_code,
uint32_t engine_error_code = 0, uint32_t engine_error_code = 0,
@ -705,7 +706,8 @@ const char* error_messages[] = {nullptr,
"A boolean was expected", "A boolean was expected",
"An array was expected", "An array was expected",
"Unknown failure", "Unknown failure",
"An exception is pending"}; "An exception is pending",
"The async work item was cancelled"};
void napi_clear_last_error(napi_env env) { void napi_clear_last_error(napi_env env) {
env->last_error.error_code = napi_ok; 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); 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;
}

15
src/node_api.h

@ -457,6 +457,21 @@ NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env,
void** data, void** data,
napi_value* arraybuffer, napi_value* arraybuffer,
size_t* byte_offset); 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 EXTERN_C_END
#endif // SRC_NODE_API_H__ #endif // SRC_NODE_API_H__

47
src/node_api_types.h

@ -16,12 +16,7 @@ typedef struct napi_ref__ *napi_ref;
typedef struct napi_handle_scope__ *napi_handle_scope; typedef struct napi_handle_scope__ *napi_handle_scope;
typedef struct napi_escapable_handle_scope__ *napi_escapable_handle_scope; typedef struct napi_escapable_handle_scope__ *napi_escapable_handle_scope;
typedef struct napi_callback_info__ *napi_callback_info; typedef struct napi_callback_info__ *napi_callback_info;
typedef struct napi_async_work__ *napi_async_work;
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 enum { typedef enum {
napi_default = 0, napi_default = 0,
@ -34,20 +29,6 @@ typedef enum {
napi_static = 1 << 10, napi_static = 1 << 10,
} napi_property_attributes; } 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 { typedef enum {
// ES6 types (corresponds to typeof) // ES6 types (corresponds to typeof)
napi_undefined, napi_undefined,
@ -85,9 +66,35 @@ typedef enum {
napi_array_expected, napi_array_expected,
napi_generic_failure, napi_generic_failure,
napi_pending_exception, napi_pending_exception,
napi_cancelled,
napi_status_last napi_status_last
} napi_status; } 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 { typedef struct {
const char* error_message; const char* error_message;
void* engine_reserved; void* engine_reserved;

8
test/addons-napi/test_async/binding.gyp

@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_async",
"sources": [ "test_async.cc" ]
}
]
}

10
test/addons-napi/test_async/test.js

@ -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

@ -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)
Loading…
Cancel
Save