Skip to content

Commit 622a125

Browse files
committed
add AsyncResource convenience class
1 parent 9f546fe commit 622a125

File tree

8 files changed

+263
-21
lines changed

8 files changed

+263
-21
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ LINT_SOURCES = \
3535
nan_weak.h \
3636
test/cpp/accessors.cpp \
3737
test/cpp/accessors2.cpp \
38+
test/cpp/asyncresource.cpp \
3839
test/cpp/asyncworker.cpp \
3940
test/cpp/asyncprogressworker.cpp \
4041
test/cpp/asyncprogressworkerstream.cpp \

doc/node_misc.md

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
## Miscellaneous Node Helpers
22

3-
- <a href="#api_nan_make_callback"><b><code>Nan::MakeCallback()</code></b></a>
43
- <a href="#api_nan_async_init"><b><code>Nan::AsyncInit()</code></b></a>
54
- <a href="#api_nan_async_destory"><b><code>Nan::AsyncDestory()</code></b></a>
5+
- <a href="#api_nan_asyncresource"><b><code>Nan::AsyncResource</code></b></a>
6+
- <a href="#api_nan_make_callback"><b><code>Nan::MakeCallback()</code></b></a>
67
- <a href="#api_nan_module_init"><b><code>NAN_MODULE_INIT()</code></b></a>
78
- <a href="#api_nan_export"><b><code>Nan::Export()</code></b></a>
89

910

10-
<a name="api_async_init"></a>
11+
<a name="api_nan_async_init"></a>
1112
### Nan::AsyncInit()
1213

1314
When calling back into JavaScript asynchornously, special care must be taken to ensure that the runtime can properly track
@@ -35,36 +36,85 @@ Nan::async_context AsyncInit(v8::MaybeLocal<v8::Object> maybe_resource,
3536
For more details, see the Node [async_hooks][] documentation. You might also want to take a look at the documentation for the
3637
[N-API counterpart][napi]. For example usage, see the `makecallbackcontext.cpp` example in the `test/cpp` directory.
3738
38-
<a name="api_async_destory"></a>
39+
Note: It might be more convenient to use `Nan::AsyncResource` instead of using this directly.
40+
41+
<a name="api_nan_async_destory"></a>
3942
### Nan::AsyncDestroy()
4043
4144
Wrapper around `node::EmitAsyncDestroy`.
4245
46+
Note: It might be more convenient to use `Nan::AsyncResource` instead of using this directly.
47+
48+
<a name="api_nan_asyncresource"></a>
49+
### Nan::AsyncResource
50+
51+
`Nan::AsyncResouce` is a convenience class that provides RAII wrapper around `Nan::AsyncInit`, `Nan::AsyncDestroy` and `Nan::MakeCallback`. It is analogous to the `AsyncResource` JavaScript class exposed by Node's [async_hooks][] API.
52+
53+
Definition:
54+
55+
```c++
56+
class AsyncResource {
57+
public:
58+
AsyncResource(MaybeLocal<v8::Object> maybe_resource, v8::Local<v8::String> name);
59+
AsyncResource(MaybeLocal<v8::Object> maybe_resource, const char* name);
60+
~AsyncResource();
61+
62+
v8::MaybeLocal<v8::Value> runInAsyncScope(v8::Local<v8::Object> target,
63+
v8::Local<v8::Function> func,
64+
int argc,
65+
v8::Local<v8::Value>* argv,
66+
Nan::async_context async_context);
67+
v8::MaybeLocal<v8::Value> runInAsyncScope(v8::Local<v8::Object> target,
68+
v8::Local<v8::String> symbol,
69+
int argc,
70+
v8::Local<v8::Value>* argv,
71+
Nan::async_context async_context);
72+
v8::MaybeLocal<v8::Value> runInAsyncScope(v8::Local<v8::Object> target,
73+
const char* method,
74+
int argc,
75+
v8::Local<v8::Value>* argv,
76+
Nan::async_context async_context);
77+
};
78+
```
79+
* `maybe_resource`: An optional object associated with the async work that will be passed to the possible [async_hooks][]
80+
`init` hook.
81+
* `name`: Identified for the kind of resource that is being provided for diagnostics information exposed by the [async_hooks][]
82+
API. This will be passed to the possible `init` hook as the `type`. To avoid name collisions with other modules we recommend
83+
that the name include the name of the owning module as a prefix. For example `mysql` module could use something like
84+
`mysql:batch-db-query-resource`.
85+
* When calling JS on behalf of this resource, one can use `runInAsyncScope`. This will ensure that the callback runs in the
86+
correct async execution context.
87+
* `AsyncDestroy` is automatically called when an AsyncResource object is destroyed.
88+
89+
For example usage, see the `asyncresource.cpp` example in the `test/cpp` directory.
90+
4391
<a name="api_nan_make_callback"></a>
4492
### Nan::MakeCallback()
4593

94+
Note: It might be more convenient to use `Nan::AsyncResource` instead of using this directly.
95+
4696
Wrappers around `node::MakeCallback()` providing a consistent API across all supported versions of Node.
4797

4898
Use `MakeCallback()` rather than using `v8::Function#Call()` directly in order to properly process internal Node functionality including domains, async hooks, the microtask queue, and other debugging functionality.
4999

50100
Signatures:
51101

52102
```c++
53-
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
54-
v8::Local<v8::Function> func,
55-
int argc,
56-
v8::Local<v8::Value>* argv,
57-
Nan::async_context async_context);
58-
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
59-
v8::Local<v8::String> symbol,
60-
int argc,
61-
v8::Local<v8::Value>* argv,
62-
Nan::async_context async_context);
63-
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
64-
const char* method,
65-
int argc,
66-
v8::Local<v8::Value>* argv,
67-
Nan::async_context async_context);
103+
v8::MaybeLocal<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
104+
v8::Local<v8::Function> func,
105+
int argc,
106+
v8::Local<v8::Value>* argv,
107+
Nan::async_context async_context);
108+
v8::MaybeLocal<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
109+
v8::Local<v8::String> symbol,
110+
int argc,
111+
v8::Local<v8::Value>* argv,
112+
Nan::async_context async_context);
113+
v8::MaybeLocal<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
114+
const char* method,
115+
int argc,
116+
v8::Local<v8::Value>* argv,
117+
Nan::async_context async_context);
68118

69119
// Legacy versions. We recommend the async context preserving versions above.
70120
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,

nan.h

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,8 @@ class Utf8String {
13081308
inline void AsyncDestroy(async_context context) {
13091309
#if NODE_MODULE_VERSION >= NODE_8_0_MODULE_VERSION
13101310
v8::Isolate* isolate = v8::Isolate::GetCurrent();
1311-
node::async_context node_context = static_cast<node::async_context>(context);
1311+
node::async_context node_context =
1312+
static_cast<node::async_context>(context);
13121313
node::EmitAsyncDestroy(isolate, node_context);
13131314
#endif
13141315
}
@@ -1361,6 +1362,52 @@ class Utf8String {
13611362
#endif
13621363
}
13631364

1365+
// === AsyncResource ===========================================================
1366+
1367+
class AsyncResource {
1368+
public:
1369+
AsyncResource(
1370+
MaybeLocal<v8::Object> maybe_resource
1371+
, v8::Local<v8::String> resource_name) {
1372+
asyncContext = AsyncInit(maybe_resource, resource_name);
1373+
}
1374+
1375+
AsyncResource(MaybeLocal<v8::Object> maybe_resource, const char* name) {
1376+
asyncContext = AsyncInit(maybe_resource, name);
1377+
}
1378+
1379+
~AsyncResource() {
1380+
AsyncDestroy(asyncContext);
1381+
}
1382+
1383+
inline MaybeLocal<v8::Value> runInAsyncScope(
1384+
v8::Local<v8::Object> target
1385+
, v8::Local<v8::Function> func
1386+
, int argc
1387+
, v8::Local<v8::Value>* argv) {
1388+
return MakeCallback(target, func, argc, argv, asyncContext);
1389+
}
1390+
1391+
inline MaybeLocal<v8::Value> runInAsyncScope(
1392+
v8::Local<v8::Object> target
1393+
, v8::Local<v8::String> symbol
1394+
, int argc
1395+
, v8::Local<v8::Value>* argv) {
1396+
return MakeCallback(target, symbol, argc, argv, asyncContext);
1397+
}
1398+
1399+
inline MaybeLocal<v8::Value> runInAsyncScope(
1400+
v8::Local<v8::Object> target
1401+
, const char* method
1402+
, int argc
1403+
, v8::Local<v8::Value>* argv) {
1404+
return MakeCallback(target, method, argc, argv, asyncContext);
1405+
}
1406+
1407+
protected:
1408+
async_context asyncContext;
1409+
};
1410+
13641411
typedef void (*FreeCallback)(char *data, void *hint);
13651412

13661413
typedef const FunctionCallbackInfo<v8::Value>& NAN_METHOD_ARGS_TYPE;

test/binding.gyp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@
9494
"target_name" : "makecallbackcontext"
9595
, "sources" : [ "cpp/makecallbackcontext.cpp" ]
9696
}
97+
, {
98+
"target_name" : "asyncresource"
99+
, "sources" : [ "cpp/asyncresource.cpp" ]
100+
}
97101
, {
98102
"target_name" : "isolatedata"
99103
, "sources" : [ "cpp/isolatedata.cpp" ]

test/cpp/asyncresource.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*********************************************************************
2+
* NAN - Native Abstractions for Node.js
3+
*
4+
* Copyright (c) 2018 NAN contributors
5+
*
6+
* MIT License <https://github.com/nodejs/nan/blob/master/LICENSE.md>
7+
********************************************************************/
8+
9+
#include <nan.h>
10+
#include <unistd.h>
11+
12+
using namespace Nan; // NOLINT(build/namespaces)
13+
14+
class DelayRequest : public AsyncResource {
15+
public:
16+
DelayRequest(int milliseconds_, v8::Local<v8::Function> callback_)
17+
: AsyncResource(MaybeLocal<v8::Object>(), "nan:test.DelayRequest"),
18+
milliseconds(milliseconds_) {
19+
callback.Reset(callback_);
20+
request.data = this;
21+
}
22+
~DelayRequest() {
23+
callback.Reset();
24+
}
25+
26+
Persistent<v8::Function> callback;
27+
uv_work_t request;
28+
int milliseconds;
29+
};
30+
31+
void Delay(uv_work_t* req) {
32+
DelayRequest *delay_request = static_cast<DelayRequest*>(req->data);
33+
sleep(delay_request->milliseconds / 1000);
34+
}
35+
36+
void AfterDelay(uv_work_t* req, int status) {
37+
HandleScope scope;
38+
39+
DelayRequest *delay_request = static_cast<DelayRequest*>(req->data);
40+
v8::Local<v8::Function> callback = New(delay_request->callback);
41+
v8::Local<v8::Value> argv[0] = {};
42+
43+
v8::Local<v8::Object> target = New<v8::Object>();
44+
45+
// Run the callback in the async context.
46+
delay_request->runInAsyncScope(target, callback, 0, argv);
47+
48+
delete delay_request;
49+
}
50+
51+
NAN_METHOD(Delay) {
52+
int delay = To<int>(info[0]).FromJust();
53+
v8::Local<v8::Function> cb = To<v8::Function>(info[1]).ToLocalChecked();
54+
55+
DelayRequest* delay_request = new DelayRequest(delay, cb);
56+
57+
uv_queue_work(
58+
uv_default_loop()
59+
, &delay_request->request
60+
, Delay
61+
, reinterpret_cast<uv_after_work_cb>(AfterDelay));
62+
}
63+
64+
NAN_MODULE_INIT(Init) {
65+
Set(target, New<v8::String>("delay").ToLocalChecked(),
66+
GetFunction(New<v8::FunctionTemplate>(Delay)).ToLocalChecked());
67+
}
68+
69+
NODE_MODULE(asyncresource, Init)

test/cpp/makecallbackcontext.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ class DelayRequest {
1717
: milliseconds(milliseconds_) {
1818
callback.Reset(callback_);
1919
request.data = this;
20-
asyncContext = AsyncInit(MaybeLocal<v8::Object>(), "test.DelayRequest");
20+
asyncContext = AsyncInit(MaybeLocal<v8::Object>(),
21+
"nan:test.DelayRequest");
2122
}
2223
~DelayRequest() {
2324
AsyncDestroy(asyncContext);

test/js/asyncresource-test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*********************************************************************
2+
* NAN - Native Abstractions for Node.js
3+
*
4+
* Copyright (c) 2018 NAN contributors
5+
*
6+
* MIT License <https://github.com/nodejs/nan/blob/master/LICENSE.md>
7+
********************************************************************/
8+
9+
try {
10+
require('async_hooks');
11+
} catch (e) {
12+
process.exit(0);
13+
}
14+
15+
const test = require('tap').test
16+
, testRoot = require('path').resolve(__dirname, '..')
17+
, delay = require('bindings')({ module_root: testRoot, bindings: 'asyncresource' }).delay
18+
, asyncHooks = require('async_hooks');
19+
20+
test('asyncresource', function (t) {
21+
t.plan(7);
22+
23+
var resourceAsyncId;
24+
var originalExecutionAsyncId;
25+
var beforeCalled = false;
26+
var afterCalled = false;
27+
var destroyCalled = false;
28+
29+
var hooks = asyncHooks.createHook({
30+
init: function(asyncId, type, triggerAsyncId, resource) {
31+
if (type === 'nan:test.DelayRequest') {
32+
resourceAsyncId = asyncId;
33+
}
34+
},
35+
before: function(asyncId) {
36+
if (asyncId === resourceAsyncId) {
37+
beforeCalled = true;
38+
}
39+
},
40+
after: function(asyncId) {
41+
if (asyncId === resourceAsyncId) {
42+
afterCalled = true;
43+
}
44+
},
45+
destroy: function(asyncId) {
46+
if (asyncId === resourceAsyncId) {
47+
destroyCalled = true;
48+
}
49+
}
50+
51+
});
52+
hooks.enable();
53+
54+
originalExecutionAsyncId = asyncHooks.executionAsyncId();
55+
delay(1000, function() {
56+
t.equal(asyncHooks.executionAsyncId(), resourceAsyncId,
57+
'callback should have the correct execution context');
58+
t.equal(asyncHooks.triggerAsyncId(), originalExecutionAsyncId,
59+
'callback should have the correct trigger context');
60+
t.ok(beforeCalled, 'before should have been called');
61+
t.notOk(afterCalled, 'after should not have been called yet');
62+
setTimeout(function() {
63+
t.ok(afterCalled, 'after should have been called');
64+
t.ok(destroyCalled, 'destroy should have been called');
65+
t.equal(asyncHooks.triggerAsyncId(), resourceAsyncId,
66+
'setTimeout should have been triggered by the async resource');
67+
hooks.disable();
68+
}, 1);
69+
});
70+
});

test/js/makecallbackcontext-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test('makecallbackcontext', function (t) {
2828

2929
var hooks = asyncHooks.createHook({
3030
init: function(asyncId, type, triggerAsyncId, resource) {
31-
if (type === 'test.DelayRequest') {
31+
if (type === 'nan:test.DelayRequest') {
3232
resourceAsyncId = asyncId;
3333
}
3434
},

0 commit comments

Comments
 (0)