Skip to content

Commit a47fe69

Browse files
addaleaxjasnell
authored andcommitted
n-api: use AsyncResource for Work tracking
Enable combining N-API async work with async-hooks. PR-URL: #14697 Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jason Ginchereau <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent 6c520af commit a47fe69

File tree

6 files changed

+129
-20
lines changed

6 files changed

+129
-20
lines changed

doc/api/n-api.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3264,17 +3264,27 @@ callback invocation, even when it was cancelled.
32643264
### napi_create_async_work
32653265
<!-- YAML
32663266
added: v8.0.0
3267+
changes:
3268+
- version: REPLACEME
3269+
pr-url: https://github.com/nodejs/node/pull/14697
3270+
description: Added `async_resource` and `async_resource_name` parameters.
32673271
-->
32683272
```C
32693273
NAPI_EXTERN
32703274
napi_status napi_create_async_work(napi_env env,
3275+
napi_value async_resource,
3276+
const char* async_resource_name,
32713277
napi_async_execute_callback execute,
32723278
napi_async_complete_callback complete,
32733279
void* data,
32743280
napi_async_work* result);
32753281
```
32763282

32773283
- `[in] env`: The environment that the API is invoked under.
3284+
- `[in] async_resource`: An optional object associated with the async work
3285+
that will be passed to possible async_hooks [`init` hooks][].
3286+
- `[in] async_resource_name`: An identifier for the kind of resource that is
3287+
being provided for diagnostic information exposed by the `async_hooks` API.
32783288
- `[in] execute`: The native function which should be called to excute
32793289
the logic asynchronously.
32803290
- `[in] complete`: The native function which will be called when the
@@ -3290,6 +3300,14 @@ This API allocates a work object that is used to execute logic asynchronously.
32903300
It should be freed using [`napi_delete_async_work`][] once the work is no longer
32913301
required.
32923302

3303+
`async_resource_name` should be a null-terminated, UTF-8-encoded string.
3304+
3305+
*Note*: The `async_resource_name` identifier is provided by the user and should
3306+
be representative of the type of async work being performed. It is also
3307+
recommended to apply namespacing to the identifier, e.g. by including the
3308+
module name. See the [`async_hooks` documentation][async_hooks `type`]
3309+
for more information.
3310+
32933311
### napi_delete_async_work
32943312
<!-- YAML
32953313
added: v8.0.0
@@ -3644,3 +3662,5 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env,
36443662
[`napi_wrap`]: #n_api_napi_wrap
36453663

36463664
[`process.release`]: process.html#process_process_release
3665+
[`init` hooks]: async_hooks.html#async_hooks_init_asyncid_type_triggerasyncid_resource
3666+
[async_hooks `type`]: async_hooks.html#async_hooks_type

src/node_api.cc

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3251,13 +3251,18 @@ static napi_status ConvertUVErrorCode(int code) {
32513251
}
32523252

32533253
// Wrapper around uv_work_t which calls user-provided callbacks.
3254-
class Work {
3254+
class Work : public node::AsyncResource {
32553255
private:
32563256
explicit Work(napi_env env,
3257-
napi_async_execute_callback execute = nullptr,
3257+
v8::Local<v8::Object> async_resource,
3258+
const char* async_resource_name,
3259+
napi_async_execute_callback execute,
32583260
napi_async_complete_callback complete = nullptr,
32593261
void* data = nullptr)
3260-
: _env(env),
3262+
: AsyncResource(env->isolate,
3263+
async_resource,
3264+
async_resource_name),
3265+
_env(env),
32613266
_data(data),
32623267
_execute(execute),
32633268
_complete(complete) {
@@ -3269,10 +3274,13 @@ class Work {
32693274

32703275
public:
32713276
static Work* New(napi_env env,
3277+
v8::Local<v8::Object> async_resource,
3278+
const char* async_resource_name,
32723279
napi_async_execute_callback execute,
32733280
napi_async_complete_callback complete,
32743281
void* data) {
3275-
return new Work(env, execute, complete, data);
3282+
return new Work(env, async_resource, async_resource_name,
3283+
execute, complete, data);
32763284
}
32773285

32783286
static void Delete(Work* work) {
@@ -3293,6 +3301,7 @@ class Work {
32933301
// Establish a handle scope here so that every callback doesn't have to.
32943302
// Also it is needed for the exception-handling below.
32953303
v8::HandleScope scope(env->isolate);
3304+
CallbackScope callback_scope(work);
32963305

32973306
work->_complete(env, ConvertUVErrorCode(status), work->_data);
32983307

@@ -3335,6 +3344,8 @@ class Work {
33353344
} while (0)
33363345

33373346
napi_status napi_create_async_work(napi_env env,
3347+
napi_value async_resource,
3348+
const char* async_resource_name,
33383349
napi_async_execute_callback execute,
33393350
napi_async_complete_callback complete,
33403351
void* data,
@@ -3343,7 +3354,18 @@ napi_status napi_create_async_work(napi_env env,
33433354
CHECK_ARG(env, execute);
33443355
CHECK_ARG(env, result);
33453356

3346-
uvimpl::Work* work = uvimpl::Work::New(env, execute, complete, data);
3357+
v8::Local<v8::Object> resource;
3358+
if (async_resource != nullptr) {
3359+
auto value = v8impl::V8LocalValueFromJsValue(async_resource);
3360+
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
3361+
resource = value.As<v8::Object>();
3362+
} else {
3363+
resource = v8::Object::New(env->isolate);
3364+
}
3365+
3366+
uvimpl::Work* work =
3367+
uvimpl::Work::New(env, resource, async_resource_name,
3368+
execute, complete, data);
33473369

33483370
*result = reinterpret_cast<napi_async_work>(work);
33493371

src/node_api.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,8 @@ NAPI_EXTERN napi_status napi_get_dataview_info(napi_env env,
522522
// Methods to manage simple async operations
523523
NAPI_EXTERN
524524
napi_status napi_create_async_work(napi_env env,
525+
napi_value async_resource,
526+
const char* async_resource_name,
525527
napi_async_execute_callback execute,
526528
napi_async_complete_callback complete,
527529
void* data,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const assert = require('assert');
4+
const async_hooks = require('async_hooks');
5+
const test_async = require(`./build/${common.buildType}/test_async`);
6+
7+
const events = [];
8+
let testId;
9+
const initAsyncId = async_hooks.executionAsyncId();
10+
11+
async_hooks.createHook({
12+
init(id, provider, triggerAsyncId, resource) {
13+
if (provider === 'TestResource') {
14+
testId = id;
15+
events.push({ type: 'init', id, provider, triggerAsyncId, resource });
16+
}
17+
},
18+
before(id) {
19+
if (testId === id) {
20+
events.push({ type: 'before', id });
21+
}
22+
},
23+
after(id) {
24+
if (testId === id) {
25+
events.push({ type: 'after', id });
26+
}
27+
},
28+
destroy(id) {
29+
if (testId === id) {
30+
events.push({ type: 'destroy', id });
31+
}
32+
}
33+
}).enable();
34+
35+
const resource = { foo: 'foo' };
36+
37+
events.push({ type: 'start' });
38+
test_async.Test(5, resource, common.mustCall(function(err, val) {
39+
assert.strictEqual(err, null);
40+
assert.strictEqual(val, 10);
41+
events.push({ type: 'complete' });
42+
process.nextTick(common.mustCall());
43+
}));
44+
events.push({ type: 'scheduled' });
45+
46+
process.on('exit', () => {
47+
assert.deepStrictEqual(events, [
48+
{ type: 'start' },
49+
{ type: 'init',
50+
id: testId,
51+
provider: 'TestResource',
52+
triggerAsyncId: initAsyncId,
53+
resource },
54+
{ type: 'scheduled' },
55+
{ type: 'before', id: testId },
56+
{ type: 'complete' },
57+
{ type: 'after', id: testId },
58+
{ type: 'destroy', id: testId }
59+
]);
60+
});

test/addons-napi/test_async/test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const testException = 'test_async_cb_exception';
99
// Exception thrown from async completion callback.
1010
// (Tested in a spawned process because the exception is fatal.)
1111
if (process.argv[2] === 'child') {
12-
test_async.Test(1, common.mustCall(function() {
12+
test_async.Test(1, {}, common.mustCall(function() {
1313
throw new Error(testException);
1414
}));
1515
return;
@@ -20,7 +20,7 @@ assert.ifError(p.error);
2020
assert.ok(p.stderr.toString().includes(testException));
2121

2222
// Successful async execution and completion callback.
23-
test_async.Test(5, common.mustCall(function(err, val) {
23+
test_async.Test(5, {}, common.mustCall(function(err, val) {
2424
assert.strictEqual(err, null);
2525
assert.strictEqual(val, 10);
2626
process.nextTick(common.mustCall());

test/addons-napi/test_async/test_async.cc

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,37 +61,40 @@ void Complete(napi_env env, napi_status status, void* data) {
6161

6262
napi_value result;
6363
NAPI_CALL_RETURN_VOID(env,
64-
napi_make_callback(env, global, callback, 2, argv, &result));
64+
napi_call_function(env, global, callback, 2, argv, &result));
6565

6666
NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback));
6767
NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
6868
}
6969

7070
napi_value Test(napi_env env, napi_callback_info info) {
71-
size_t argc = 2;
72-
napi_value argv[2];
71+
size_t argc = 3;
72+
napi_value argv[3];
7373
napi_value _this;
7474
void* data;
7575
NAPI_CALL(env,
7676
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
77-
NAPI_ASSERT(env, argc >= 2, "Not enough arguments, expected 2.");
77+
NAPI_ASSERT(env, argc >= 3, "Not enough arguments, expected 2.");
7878

7979
napi_valuetype t;
8080
NAPI_CALL(env, napi_typeof(env, argv[0], &t));
8181
NAPI_ASSERT(env, t == napi_number,
8282
"Wrong first argument, integer expected.");
8383
NAPI_CALL(env, napi_typeof(env, argv[1], &t));
84+
NAPI_ASSERT(env, t == napi_object,
85+
"Wrong second argument, object expected.");
86+
NAPI_CALL(env, napi_typeof(env, argv[2], &t));
8487
NAPI_ASSERT(env, t == napi_function,
85-
"Wrong second argument, function expected.");
88+
"Wrong third argument, function expected.");
8689

8790
the_carrier._output = 0;
8891

8992
NAPI_CALL(env,
9093
napi_get_value_int32(env, argv[0], &the_carrier._input));
9194
NAPI_CALL(env,
92-
napi_create_reference(env, argv[1], 1, &the_carrier._callback));
93-
NAPI_CALL(env, napi_create_async_work(
94-
env, Execute, Complete, &the_carrier, &the_carrier._request));
95+
napi_create_reference(env, argv[2], 1, &the_carrier._callback));
96+
NAPI_CALL(env, napi_create_async_work(env, argv[1], "TestResource",
97+
Execute, Complete, &the_carrier, &the_carrier._request));
9598
NAPI_CALL(env,
9699
napi_queue_async_work(env, the_carrier._request));
97100

@@ -116,7 +119,7 @@ void CancelComplete(napi_env env, napi_status status, void* data) {
116119
NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &global));
117120
napi_value result;
118121
NAPI_CALL_RETURN_VOID(env,
119-
napi_make_callback(env, global, callback, 0, nullptr, &result));
122+
napi_call_function(env, global, callback, 0, nullptr, &result));
120123
}
121124

122125
NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
@@ -140,8 +143,9 @@ napi_value TestCancel(napi_env env, napi_callback_info info) {
140143
// make sure the work we are going to cancel will not be
141144
// able to start by using all the threads in the pool
142145
for (int i = 1; i < MAX_CANCEL_THREADS; i++) {
143-
NAPI_CALL(env, napi_create_async_work(env, CancelExecute,
144-
BusyCancelComplete, &async_carrier[i], &async_carrier[i]._request));
146+
NAPI_CALL(env, napi_create_async_work(env, nullptr, "TestCancelBusy",
147+
CancelExecute, BusyCancelComplete,
148+
&async_carrier[i], &async_carrier[i]._request));
145149
NAPI_CALL(env, napi_queue_async_work(env, async_carrier[i]._request));
146150
}
147151

@@ -151,8 +155,9 @@ napi_value TestCancel(napi_env env, napi_callback_info info) {
151155
// workers above.
152156
NAPI_CALL(env,
153157
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
154-
NAPI_CALL(env, napi_create_async_work(env, CancelExecute,
155-
CancelComplete, &async_carrier[0], &async_carrier[0]._request));
158+
NAPI_CALL(env, napi_create_async_work(env, nullptr, "TestCancelled",
159+
CancelExecute, CancelComplete,
160+
&async_carrier[0], &async_carrier[0]._request));
156161
NAPI_CALL(env,
157162
napi_create_reference(env, argv[0], 1, &async_carrier[0]._callback));
158163
NAPI_CALL(env, napi_queue_async_work(env, async_carrier[0]._request));

0 commit comments

Comments
 (0)