Skip to content

Commit

Permalink
n-api: implement async helper methods
Browse files Browse the repository at this point in the history
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: #12250
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Michael Dawson <[email protected]>
Reviewed-By: Timothy Gu <[email protected]>
Reviewed-By: Hitesh Kanwathirtha <[email protected]>
  • Loading branch information
boingoing authored and addaleax committed Apr 10, 2017
1 parent ca786c3 commit 9decfb1
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 21 deletions.
137 changes: 136 additions & 1 deletion src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
15 changes: 15 additions & 0 deletions src/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__
47 changes: 27 additions & 20 deletions src/node_api_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions test/addons-napi/test_async/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_async",
"sources": [ "test_async.cc" ]
}
]
}
10 changes: 10 additions & 0 deletions test/addons-napi/test_async/test.js
Original file line number Diff line number Diff line change
@@ -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 changes: 122 additions & 0 deletions test/addons-napi/test_async/test_async.cc
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 9decfb1

Please sign in to comment.