Skip to content

n-api: implement async helper methods #12250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this moved?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put the napi_callback typedef near napi_async_execute_callback and other callback typedefs. But napi_async_complete_callback takes a napi_status so all the callbacks should be located down below the enum (at least). However, napi_property_descriptor has napi_callback members so it also had to move down in the file.


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)