n-api: use AsyncResource for Work tracking · nodejs/node@a47fe69 · GitHub
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 <tniessen@tnie.de> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jason Ginchereau <jasongin@microsoft.com> Reviewed-By: Michael Dawson <mhdawson@ibm.com>
1 parent 6c520af commit a47fe69

6 files changed

Lines changed: 129 additions & 20 deletions

File tree

doc/api/n-api.md

Lines changed: 20 additions & 0 deletions

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

0 commit comments

Comments
 (0)