Permalink
Browse files

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: #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>
  • Loading branch information...
boingoing authored and addaleax committed Mar 28, 2017
1 parent ca786c3 commit 9decfb15215978b73094ffb7a4bdf2a0da010258
View
@@ -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;
+}
View
@@ -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__
View
@@ -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;
@@ -0,0 +1,8 @@
+{
+ "targets": [
+ {
+ "target_name": "test_async",
+ "sources": [ "test_async.cc" ]
+ }
+ ]
+}
@@ -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() {}));
+}));
@@ -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.