Skip to content

Commit d82fd2a

Browse files
boingoingMylesBorins
authored andcommittedApr 16, 2018
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. Backport-PR-URL: #19447 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>
1 parent c127b71 commit d82fd2a

File tree

6 files changed

+318
-21
lines changed

6 files changed

+318
-21
lines changed
 

‎src/node_api.cc

+136-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <cmath>
1616
#include <vector>
1717
#include "node_api.h"
18+
#include "env-inl.h"
1819
#include "node_api_backport.h"
1920

2021
static
@@ -707,7 +708,8 @@ const char* error_messages[] = {nullptr,
707708
"A boolean was expected",
708709
"An array was expected",
709710
"Unknown failure",
710-
"An exception is pending"};
711+
"An exception is pending",
712+
"The async work item was cancelled"};
711713

712714
void napi_clear_last_error(napi_env env) {
713715
env->last_error.error_code = napi_ok;
@@ -2576,3 +2578,136 @@ napi_status napi_get_typedarray_info(napi_env env,
25762578

25772579
return GET_RETURN_STATUS(env);
25782580
}
2581+
2582+
namespace uvimpl {
2583+
2584+
napi_status ConvertUVErrorCode(int code) {
2585+
switch (code) {
2586+
case 0:
2587+
return napi_ok;
2588+
case UV_EINVAL:
2589+
return napi_invalid_arg;
2590+
case UV_ECANCELED:
2591+
return napi_cancelled;
2592+
}
2593+
2594+
return napi_generic_failure;
2595+
}
2596+
2597+
// Wrapper around uv_work_t which calls user-provided callbacks.
2598+
class Work {
2599+
private:
2600+
explicit Work(napi_env env,
2601+
napi_async_execute_callback execute = nullptr,
2602+
napi_async_complete_callback complete = nullptr,
2603+
void* data = nullptr)
2604+
: _env(env),
2605+
_data(data),
2606+
_execute(execute),
2607+
_complete(complete) {
2608+
_request.data = this;
2609+
}
2610+
2611+
~Work() { }
2612+
2613+
public:
2614+
static Work* New(napi_env env,
2615+
napi_async_execute_callback execute,
2616+
napi_async_complete_callback complete,
2617+
void* data) {
2618+
return new Work(env, execute, complete, data);
2619+
}
2620+
2621+
static void Delete(Work* work) {
2622+
delete work;
2623+
}
2624+
2625+
static void ExecuteCallback(uv_work_t* req) {
2626+
Work* work = static_cast<Work*>(req->data);
2627+
work->_execute(work->_env, work->_data);
2628+
}
2629+
2630+
static void CompleteCallback(uv_work_t* req, int status) {
2631+
Work* work = static_cast<Work*>(req->data);
2632+
2633+
if (work->_complete != nullptr) {
2634+
work->_complete(work->_env, ConvertUVErrorCode(status), work->_data);
2635+
}
2636+
}
2637+
2638+
uv_work_t* Request() {
2639+
return &_request;
2640+
}
2641+
2642+
private:
2643+
napi_env _env;
2644+
void* _data;
2645+
uv_work_t _request;
2646+
napi_async_execute_callback _execute;
2647+
napi_async_complete_callback _complete;
2648+
};
2649+
2650+
} // end of namespace uvimpl
2651+
2652+
#define CALL_UV(env, condition) \
2653+
do { \
2654+
int result = (condition); \
2655+
napi_status status = uvimpl::ConvertUVErrorCode(result); \
2656+
if (status != napi_ok) { \
2657+
return napi_set_last_error(env, status, result); \
2658+
} \
2659+
} while (0)
2660+
2661+
napi_status napi_create_async_work(napi_env env,
2662+
napi_async_execute_callback execute,
2663+
napi_async_complete_callback complete,
2664+
void* data,
2665+
napi_async_work* result) {
2666+
CHECK_ENV(env);
2667+
CHECK_ARG(env, execute);
2668+
CHECK_ARG(env, result);
2669+
2670+
uvimpl::Work* work = uvimpl::Work::New(env, execute, complete, data);
2671+
2672+
*result = reinterpret_cast<napi_async_work>(work);
2673+
2674+
return napi_ok;
2675+
}
2676+
2677+
napi_status napi_delete_async_work(napi_env env, napi_async_work work) {
2678+
CHECK_ENV(env);
2679+
CHECK_ARG(env, work);
2680+
2681+
uvimpl::Work::Delete(reinterpret_cast<uvimpl::Work*>(work));
2682+
2683+
return napi_ok;
2684+
}
2685+
2686+
napi_status napi_queue_async_work(napi_env env, napi_async_work work) {
2687+
CHECK_ENV(env);
2688+
CHECK_ARG(env, work);
2689+
2690+
// Consider: Encapsulate the uv_loop_t into an opaque pointer parameter
2691+
uv_loop_t* event_loop =
2692+
node::Environment::GetCurrent(env->isolate)->event_loop();
2693+
2694+
uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
2695+
2696+
CALL_UV(env, uv_queue_work(event_loop,
2697+
w->Request(),
2698+
uvimpl::Work::ExecuteCallback,
2699+
uvimpl::Work::CompleteCallback));
2700+
2701+
return napi_ok;
2702+
}
2703+
2704+
napi_status napi_cancel_async_work(napi_env env, napi_async_work work) {
2705+
CHECK_ENV(env);
2706+
CHECK_ARG(env, work);
2707+
2708+
uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
2709+
2710+
CALL_UV(env, uv_cancel(reinterpret_cast<uv_req_t*>(w->Request())));
2711+
2712+
return napi_ok;
2713+
}

‎src/node_api.h

+15
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,21 @@ NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env,
457457
void** data,
458458
napi_value* arraybuffer,
459459
size_t* byte_offset);
460+
461+
// Methods to manage simple async operations
462+
NAPI_EXTERN
463+
napi_status napi_create_async_work(napi_env env,
464+
napi_async_execute_callback execute,
465+
napi_async_complete_callback complete,
466+
void* data,
467+
napi_async_work* result);
468+
NAPI_EXTERN napi_status napi_delete_async_work(napi_env env,
469+
napi_async_work work);
470+
NAPI_EXTERN napi_status napi_queue_async_work(napi_env env,
471+
napi_async_work work);
472+
NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env,
473+
napi_async_work work);
474+
460475
EXTERN_C_END
461476

462477
#endif // SRC_NODE_API_H__

‎src/node_api_types.h

+27-20
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ typedef struct napi_ref__ *napi_ref;
1616
typedef struct napi_handle_scope__ *napi_handle_scope;
1717
typedef struct napi_escapable_handle_scope__ *napi_escapable_handle_scope;
1818
typedef struct napi_callback_info__ *napi_callback_info;
19-
20-
typedef napi_value (*napi_callback)(napi_env env,
21-
napi_callback_info info);
22-
typedef void (*napi_finalize)(napi_env env,
23-
void* finalize_data,
24-
void* finalize_hint);
19+
typedef struct napi_async_work__ *napi_async_work;
2520

2621
typedef enum {
2722
napi_default = 0,
@@ -34,20 +29,6 @@ typedef enum {
3429
napi_static = 1 << 10,
3530
} napi_property_attributes;
3631

37-
typedef struct {
38-
// One of utf8name or name should be NULL.
39-
const char* utf8name;
40-
napi_value name;
41-
42-
napi_callback method;
43-
napi_callback getter;
44-
napi_callback setter;
45-
napi_value value;
46-
47-
napi_property_attributes attributes;
48-
void* data;
49-
} napi_property_descriptor;
50-
5132
typedef enum {
5233
// ES6 types (corresponds to typeof)
5334
napi_undefined,
@@ -85,9 +66,35 @@ typedef enum {
8566
napi_array_expected,
8667
napi_generic_failure,
8768
napi_pending_exception,
69+
napi_cancelled,
8870
napi_status_last
8971
} napi_status;
9072

73+
typedef napi_value (*napi_callback)(napi_env env,
74+
napi_callback_info info);
75+
typedef void (*napi_finalize)(napi_env env,
76+
void* finalize_data,
77+
void* finalize_hint);
78+
typedef void (*napi_async_execute_callback)(napi_env env,
79+
void* data);
80+
typedef void (*napi_async_complete_callback)(napi_env env,
81+
napi_status status,
82+
void* data);
83+
84+
typedef struct {
85+
// One of utf8name or name should be NULL.
86+
const char* utf8name;
87+
napi_value name;
88+
89+
napi_callback method;
90+
napi_callback getter;
91+
napi_callback setter;
92+
napi_value value;
93+
94+
napi_property_attributes attributes;
95+
void* data;
96+
} napi_property_descriptor;
97+
9198
typedef struct {
9299
const char* error_message;
93100
void* engine_reserved;
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "test_async",
5+
"sources": [ "test_async.cc" ]
6+
}
7+
]
8+
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const assert = require('assert');
4+
const test_async = require(`./build/${common.buildType}/test_async`);
5+
6+
test_async(5, common.mustCall(function(err, val) {
7+
assert.strictEqual(err, null);
8+
assert.strictEqual(val, 10);
9+
process.nextTick(common.mustCall(function() {}));
10+
}));
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#include <node_api.h>
2+
#include "../common.h"
3+
4+
#if defined _WIN32
5+
#include <windows.h>
6+
#else
7+
#include <unistd.h>
8+
#endif
9+
10+
typedef struct {
11+
int32_t _input;
12+
int32_t _output;
13+
napi_ref _callback;
14+
napi_async_work _request;
15+
} carrier;
16+
17+
carrier the_carrier;
18+
19+
struct AutoHandleScope {
20+
explicit AutoHandleScope(napi_env env)
21+
: _env(env),
22+
_scope(nullptr) {
23+
napi_open_handle_scope(_env, &_scope);
24+
}
25+
~AutoHandleScope() {
26+
napi_close_handle_scope(_env, _scope);
27+
}
28+
private:
29+
AutoHandleScope() { }
30+
31+
napi_env _env;
32+
napi_handle_scope _scope;
33+
};
34+
35+
void Execute(napi_env env, void* data) {
36+
#if defined _WIN32
37+
Sleep(1000);
38+
#else
39+
sleep(1);
40+
#endif
41+
carrier* c = static_cast<carrier*>(data);
42+
43+
if (c != &the_carrier) {
44+
napi_throw_type_error(env, "Wrong data parameter to Execute.");
45+
return;
46+
}
47+
48+
c->_output = c->_input * 2;
49+
}
50+
51+
void Complete(napi_env env, napi_status status, void* data) {
52+
AutoHandleScope scope(env);
53+
carrier* c = static_cast<carrier*>(data);
54+
55+
if (c != &the_carrier) {
56+
napi_throw_type_error(env, "Wrong data parameter to Complete.");
57+
return;
58+
}
59+
60+
if (status != napi_ok) {
61+
napi_throw_type_error(env, "Execute callback failed.");
62+
return;
63+
}
64+
65+
napi_value argv[2];
66+
67+
NAPI_CALL_RETURN_VOID(env, napi_get_null(env, &argv[0]));
68+
NAPI_CALL_RETURN_VOID(env, napi_create_number(env, c->_output, &argv[1]));
69+
napi_value callback;
70+
NAPI_CALL_RETURN_VOID(env,
71+
napi_get_reference_value(env, c->_callback, &callback));
72+
napi_value global;
73+
NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &global));
74+
75+
napi_value result;
76+
NAPI_CALL_RETURN_VOID(env,
77+
napi_call_function(env, global, callback, 2, argv, &result));
78+
79+
NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback));
80+
NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
81+
}
82+
83+
napi_value Test(napi_env env, napi_callback_info info) {
84+
size_t argc = 2;
85+
napi_value argv[2];
86+
napi_value _this;
87+
void* data;
88+
NAPI_CALL(env,
89+
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
90+
NAPI_ASSERT(env, argc >= 2, "Not enough arguments, expected 2.");
91+
92+
napi_valuetype t;
93+
NAPI_CALL(env, napi_typeof(env, argv[0], &t));
94+
NAPI_ASSERT(env, t == napi_number,
95+
"Wrong first argument, integer expected.");
96+
NAPI_CALL(env, napi_typeof(env, argv[1], &t));
97+
NAPI_ASSERT(env, t == napi_function,
98+
"Wrong second argument, function expected.");
99+
100+
the_carrier._output = 0;
101+
102+
NAPI_CALL(env,
103+
napi_get_value_int32(env, argv[0], &the_carrier._input));
104+
NAPI_CALL(env,
105+
napi_create_reference(env, argv[1], 1, &the_carrier._callback));
106+
NAPI_CALL(env, napi_create_async_work(
107+
env, Execute, Complete, &the_carrier, &the_carrier._request));
108+
NAPI_CALL(env,
109+
napi_queue_async_work(env, the_carrier._request));
110+
111+
return nullptr;
112+
}
113+
114+
void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
115+
napi_value test;
116+
NAPI_CALL_RETURN_VOID(env,
117+
napi_create_function(env, "Test", Test, nullptr, &test));
118+
NAPI_CALL_RETURN_VOID(env,
119+
napi_set_named_property(env, module, "exports", test));
120+
}
121+
122+
NAPI_MODULE(addon, Init)

0 commit comments

Comments
 (0)
Please sign in to comment.