Skip to content

Commit 9decfb1

Browse files
boingoingaddaleax
authored andcommitted
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>
1 parent ca786c3 commit 9decfb1

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

1920
napi_status napi_set_last_error(napi_env env, napi_status error_code,
2021
uint32_t engine_error_code = 0,
@@ -705,7 +706,8 @@ const char* error_messages[] = {nullptr,
705706
"A boolean was expected",
706707
"An array was expected",
707708
"Unknown failure",
708-
"An exception is pending"};
709+
"An exception is pending",
710+
"The async work item was cancelled"};
709711

710712
void napi_clear_last_error(napi_env env) {
711713
env->last_error.error_code = napi_ok;
@@ -2574,3 +2576,136 @@ napi_status napi_get_typedarray_info(napi_env env,
25742576

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

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)