Skip to content

Commit

Permalink
add AsyncResource convenience class
Browse files Browse the repository at this point in the history
  • Loading branch information
ofrobots committed Feb 7, 2018
1 parent 9f546fe commit 10a38e8
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 21 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ LINT_SOURCES = \
nan_weak.h \
test/cpp/accessors.cpp \
test/cpp/accessors2.cpp \
test/cpp/asyncresource.cpp \
test/cpp/asyncworker.cpp \
test/cpp/asyncprogressworker.cpp \
test/cpp/asyncprogressworkerstream.cpp \
Expand Down
86 changes: 68 additions & 18 deletions doc/node_misc.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
## Miscellaneous Node Helpers

- <a href="#api_nan_make_callback"><b><code>Nan::MakeCallback()</code></b></a>
- <a href="#api_nan_async_init"><b><code>Nan::AsyncInit()</code></b></a>
- <a href="#api_nan_async_destory"><b><code>Nan::AsyncDestory()</code></b></a>
- <a href="#api_nan_asyncresource"><b><code>Nan::AsyncResource</code></b></a>
- <a href="#api_nan_make_callback"><b><code>Nan::MakeCallback()</code></b></a>
- <a href="#api_nan_module_init"><b><code>NAN_MODULE_INIT()</code></b></a>
- <a href="#api_nan_export"><b><code>Nan::Export()</code></b></a>


<a name="api_async_init"></a>
<a name="api_nan_async_init"></a>
### Nan::AsyncInit()

When calling back into JavaScript asynchornously, special care must be taken to ensure that the runtime can properly track
Expand Down Expand Up @@ -35,36 +36,85 @@ Nan::async_context AsyncInit(v8::MaybeLocal<v8::Object> maybe_resource,
For more details, see the Node [async_hooks][] documentation. You might also want to take a look at the documentation for the
[N-API counterpart][napi]. For example usage, see the `makecallbackcontext.cpp` example in the `test/cpp` directory.
<a name="api_async_destory"></a>
Note: It might be more convenient to use `Nan::AsyncResource` instead of using this directly.
<a name="api_nan_async_destory"></a>
### Nan::AsyncDestroy()
Wrapper around `node::EmitAsyncDestroy`.
Note: It might be more convenient to use `Nan::AsyncResource` instead of using this directly.
<a name="api_nan_asyncresource"></a>
### Nan::AsyncResource
`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.
Definition:
```c++
class AsyncResource {
public:
AsyncResource(MaybeLocal<v8::Object> maybe_resource, v8::Local<v8::String> name);
AsyncResource(MaybeLocal<v8::Object> maybe_resource, const char* name);
~AsyncResource();
v8::MaybeLocal<v8::Value> runInAsyncScope(v8::Local<v8::Object> target,
v8::Local<v8::Function> func,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
v8::MaybeLocal<v8::Value> runInAsyncScope(v8::Local<v8::Object> target,
v8::Local<v8::String> symbol,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
v8::MaybeLocal<v8::Value> runInAsyncScope(v8::Local<v8::Object> target,
const char* method,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
};
```
* `maybe_resource`: An optional object associated with the async work that will be passed to the possible [async_hooks][]
`init` hook.
* `name`: Identified for the kind of resource that is being provided for diagnostics information exposed by the [async_hooks][]
API. This will be passed to the possible `init` hook as the `type`. To avoid name collisions with other modules we recommend
that the name include the name of the owning module as a prefix. For example `mysql` module could use something like
`mysql:batch-db-query-resource`.
* When calling JS on behalf of this resource, one can use `runInAsyncScope`. This will ensure that the callback runs in the
correct async execution context.
* `AsyncDestroy` is automatically called when an AsyncResource object is destroyed.

For example usage, see the `asyncresource.cpp` example in the `test/cpp` directory.

<a name="api_nan_make_callback"></a>
### Nan::MakeCallback()

Note: It might be more convenient to use `Nan::AsyncResource` instead of using this directly.

Wrappers around `node::MakeCallback()` providing a consistent API across all supported versions of Node.

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.

Signatures:

```c++
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
v8::Local<v8::Function> func,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
v8::Local<v8::String> symbol,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
const char* method,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
v8::MaybeLocal<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
v8::Local<v8::Function> func,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
v8::MaybeLocal<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
v8::Local<v8::String> symbol,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);
v8::MaybeLocal<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
const char* method,
int argc,
v8::Local<v8::Value>* argv,
Nan::async_context async_context);

// Legacy versions. We recommend the async context preserving versions above.
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
Expand Down
51 changes: 50 additions & 1 deletion nan.h
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,7 @@ class Utf8String {

node::async_context context =
node::EmitAsyncInit(isolate, resource, resource_name);
printf("in AsyncInit, context is <%f,%f>\n", context.async_id, context.trigger_async_id);
return static_cast<async_context>(context);
#endif
}
Expand All @@ -1308,7 +1309,8 @@ class Utf8String {
inline void AsyncDestroy(async_context context) {
#if NODE_MODULE_VERSION >= NODE_8_0_MODULE_VERSION
v8::Isolate* isolate = v8::Isolate::GetCurrent();
node::async_context node_context = static_cast<node::async_context>(context);
node::async_context node_context =
static_cast<node::async_context>(context);
node::EmitAsyncDestroy(isolate, node_context);
#endif
}
Expand All @@ -1323,6 +1325,7 @@ class Utf8String {
// Ignore the async_context value.
return MakeCallback(target, func, argc, argv);
#else
printf("in MakeCallback, context is <%f,%f>\n", asyncContext.async_id, asyncContext.trigger_async_id);
return node::MakeCallback(
v8::Isolate::GetCurrent(), target, func, argc, argv,
static_cast<node::async_context>(asyncContext));
Expand Down Expand Up @@ -1361,6 +1364,52 @@ class Utf8String {
#endif
}

// === AsyncResource ===========================================================

class AsyncResource {
public:
AsyncResource(
MaybeLocal<v8::Object> maybe_resource
, v8::Local<v8::String> resource_name) {
asyncContext = AsyncInit(maybe_resource, resource_name);
}

AsyncResource(MaybeLocal<v8::Object> maybe_resource, const char* name) {
asyncContext = AsyncInit(maybe_resource, name);
}

~AsyncResource() {
AsyncDestroy(asyncContext);
}

inline MaybeLocal<v8::Value> runInAsyncScope(
v8::Local<v8::Object> target
, v8::Local<v8::Function> func
, int argc
, v8::Local<v8::Value>* argv) {
return MakeCallback(target, func, argc, argv, asyncContext);
}

inline MaybeLocal<v8::Value> runInAsyncScope(
v8::Local<v8::Object> target
, v8::Local<v8::String> symbol
, int argc
, v8::Local<v8::Value>* argv) {
return MakeCallback(target, symbol, argc, argv, asyncContext);
}

inline MaybeLocal<v8::Value> runInAsyncScope(
v8::Local<v8::Object> target
, const char* method
, int argc
, v8::Local<v8::Value>* argv) {
return MakeCallback(target, method, argc, argv, asyncContext);
}

protected:
async_context asyncContext;
};

typedef void (*FreeCallback)(char *data, void *hint);

typedef const FunctionCallbackInfo<v8::Value>& NAN_METHOD_ARGS_TYPE;
Expand Down
4 changes: 4 additions & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@
"target_name" : "makecallbackcontext"
, "sources" : [ "cpp/makecallbackcontext.cpp" ]
}
, {
"target_name" : "asyncresource"
, "sources" : [ "cpp/asyncresource.cpp" ]
}
, {
"target_name" : "isolatedata"
, "sources" : [ "cpp/isolatedata.cpp" ]
Expand Down
69 changes: 69 additions & 0 deletions test/cpp/asyncresource.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*********************************************************************
* NAN - Native Abstractions for Node.js
*
* Copyright (c) 2018 NAN contributors
*
* MIT License <https://github.com/nodejs/nan/blob/master/LICENSE.md>
********************************************************************/

#include <nan.h>
#include <unistd.h>

using namespace Nan; // NOLINT(build/namespaces)

class DelayRequest : public AsyncResource {
public:
DelayRequest(int milliseconds_, v8::Local<v8::Function> callback_)
: AsyncResource(MaybeLocal<v8::Object>(), "nan:test.DelayRequest"),
milliseconds(milliseconds_) {
callback.Reset(callback_);
request.data = this;
}
~DelayRequest() {
callback.Reset();
}

Persistent<v8::Function> callback;
uv_work_t request;
int milliseconds;
};

void Delay(uv_work_t* req) {
DelayRequest *delay_request = static_cast<DelayRequest*>(req->data);
sleep(delay_request->milliseconds / 1000);
}

void AfterDelay(uv_work_t* req, int status) {
HandleScope scope;

DelayRequest *delay_request = static_cast<DelayRequest*>(req->data);
v8::Local<v8::Function> callback = New(delay_request->callback);
v8::Local<v8::Value> argv[0] = {};

v8::Local<v8::Object> target = New<v8::Object>();

// Run the callback in the async context.
delay_request->runInAsyncScope(target, callback, 0, argv);

delete delay_request;
}

NAN_METHOD(Delay) {
int delay = To<int>(info[0]).FromJust();
v8::Local<v8::Function> cb = To<v8::Function>(info[1]).ToLocalChecked();

DelayRequest* delay_request = new DelayRequest(delay, cb);

uv_queue_work(
uv_default_loop()
, &delay_request->request
, Delay
, reinterpret_cast<uv_after_work_cb>(AfterDelay));
}

NAN_MODULE_INIT(Init) {
Set(target, New<v8::String>("delay").ToLocalChecked(),
GetFunction(New<v8::FunctionTemplate>(Delay)).ToLocalChecked());
}

NODE_MODULE(asyncresource, Init)
3 changes: 2 additions & 1 deletion test/cpp/makecallbackcontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class DelayRequest {
: milliseconds(milliseconds_) {
callback.Reset(callback_);
request.data = this;
asyncContext = AsyncInit(MaybeLocal<v8::Object>(), "test.DelayRequest");
asyncContext = AsyncInit(MaybeLocal<v8::Object>(),
"nan:test.DelayRequest");
}
~DelayRequest() {
AsyncDestroy(asyncContext);
Expand Down
70 changes: 70 additions & 0 deletions test/js/asyncresource-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*********************************************************************
* NAN - Native Abstractions for Node.js
*
* Copyright (c) 2018 NAN contributors
*
* MIT License <https://github.com/nodejs/nan/blob/master/LICENSE.md>
********************************************************************/

try {
require('async_hooks');
} catch (e) {
process.exit(0);
}

const test = require('tap').test
, testRoot = require('path').resolve(__dirname, '..')
, delay = require('bindings')({ module_root: testRoot, bindings: 'asyncresource' }).delay
, asyncHooks = require('async_hooks');

test('asyncresource', function (t) {
t.plan(7);

var resourceAsyncId;
var originalExecutionAsyncId;
var beforeCalled = false;
var afterCalled = false;
var destroyCalled = false;

var hooks = asyncHooks.createHook({
init: function(asyncId, type, triggerAsyncId, resource) {
if (type === 'nan:test.DelayRequest') {
resourceAsyncId = asyncId;
}
},
before: function(asyncId) {
if (asyncId === resourceAsyncId) {
beforeCalled = true;
}
},
after: function(asyncId) {
if (asyncId === resourceAsyncId) {
afterCalled = true;
}
},
destroy: function(asyncId) {
if (asyncId === resourceAsyncId) {
destroyCalled = true;
}
}

});
hooks.enable();

originalExecutionAsyncId = asyncHooks.executionAsyncId();
delay(1000, function() {
t.equal(asyncHooks.executionAsyncId(), resourceAsyncId,
'callback should have the correct execution context');
t.equal(asyncHooks.triggerAsyncId(), originalExecutionAsyncId,
'callback should have the correct trigger context');
t.ok(beforeCalled, 'before should have been called');
t.notOk(afterCalled, 'after should not have been called yet');
setTimeout(function() {
t.ok(afterCalled, 'after should have been called');
t.ok(destroyCalled, 'destroy should have been called');
t.equal(asyncHooks.triggerAsyncId(), resourceAsyncId,
'setTimeout should have been triggered by the async resource');
hooks.disable();
}, 1);
});
});
2 changes: 1 addition & 1 deletion test/js/makecallbackcontext-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test('makecallbackcontext', function (t) {

var hooks = asyncHooks.createHook({
init: function(asyncId, type, triggerAsyncId, resource) {
if (type === 'test.DelayRequest') {
if (type === 'nan:test.DelayRequest') {
resourceAsyncId = asyncId;
}
},
Expand Down

0 comments on commit 10a38e8

Please sign in to comment.