Skip to content

Commit

Permalink
add async context versions of MakeCallback
Browse files Browse the repository at this point in the history
This commit adds support for the async context accepting versions of
node::MakeCallback. An async_context concept has been added as a
wrapper around node::async_context, along with APIs for initializing
and destroying async context, similar to how [N-API][] exposes this
functionality.

Ref: nodejs/node#13254
[N-API]: https://nodejs.org/dist/latest-v9.x/docs/api/n-api.html#n_api_custom_asynchronous_operations
  • Loading branch information
ofrobots committed Feb 7, 2018
1 parent b2455ac commit 8b810a2
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ LINT_SOURCES = \
test/cpp/json-parse.cpp \
test/cpp/json-stringify.cpp \
test/cpp/makecallback.cpp \
test/cpp/makecallbackcontext.cpp \
test/cpp/morenews.cpp \
test/cpp/multifile1.cpp \
test/cpp/multifile2.cpp \
Expand Down
55 changes: 55 additions & 0 deletions doc/node_misc.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
## 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_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>
### Nan::AsyncInit()

When calling back into JavaScript asynchornously, special care must be taken to ensure that the runtime can properly track
async hops. `Nan::AsyncInit` is an object that wraps `node::EmitAsyncInit` and returns a wrapper for the
`node::async_context` structure. The `async_context` can be provided to `Nan::MakeCallback` to properly restore the correct
async execution context.

Signatures:

```c++
Nan::async_context AsyncInit(v8::MaybeLocal<v8::Object> maybe_resource,
const char* name);
Nan::async_context AsyncInit(v8::MaybeLocal<v8::Object> maybe_resource,
v8::Local<v8::String> name);
```
* `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`.
* An opaque `async_context` structure is returned. This should be passed to any `Nan::MakeCallback` operations done later.
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>
### Nan::AsyncDestroy()
Wrapper around `node::EmitAsyncDestroy`.
<a name="api_nan_make_callback"></a>
### Nan::MakeCallback()
Expand All @@ -15,6 +50,23 @@ Use `MakeCallback()` rather than using `v8::Function#Call()` directly in order t
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);
// Legacy versions. We recommend the async context preserving versions above.
v8::Local<v8::Value> Nan::MakeCallback(v8::Local<v8::Object> target,
v8::Local<v8::Function> func,
int argc,
Expand Down Expand Up @@ -61,3 +113,6 @@ NAN_MODULE_INIT(Init) {
NAN_EXPORT(target, Foo);
}
```

[async_hooks]: https://nodejs.org/dist/latest-v9.x/docs/api/async_hooks.html
[napi]: https://nodejs.org/dist/latest-v9.x/docs/api/n-api.html#n_api_custom_asynchronous_operations
88 changes: 88 additions & 0 deletions nan.h
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,94 @@ class Utf8String {

#endif // NODE_MODULE_VERSION

//=== async_context and context aware MakeCallback =============================

#if NODE_MODULE_VERSION >= NODE_8_0_MODULE_VERSION
typedef node::async_context async_context;
#else
struct async_context {};
#endif

inline async_context AsyncInit(
MaybeLocal<v8::Object> maybe_resource
, v8::Local<v8::String> resource_name) {
#if NODE_MODULE_VERSION < NODE_8_0_MODULE_VERSION
return async_context();
#else
v8::Isolate* isolate = v8::Isolate::GetCurrent();

v8::Local<v8::Object> resource =
maybe_resource.IsEmpty() ? New<v8::Object>()
: maybe_resource.ToLocalChecked();

node::async_context context =
node::EmitAsyncInit(isolate, resource, resource_name);
return static_cast<async_context>(context);
#endif
}

inline async_context AsyncInit(
MaybeLocal<v8::Object> maybe_resource
, const char* name) {
return AsyncInit(maybe_resource, New<v8::String>(name).ToLocalChecked());
}

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::EmitAsyncDestroy(isolate, node_context);
#endif
}

inline MaybeLocal<v8::Value> MakeCallback(
v8::Local<v8::Object> target
, v8::Local<v8::Function> func
, int argc
, v8::Local<v8::Value>* argv
, async_context asyncContext) {
#if NODE_MODULE_VERSION < NODE_8_0_MODULE_VERSION
// Ignore the async_context value.
return MakeCallback(target, func, argc, argv);
#else
return node::MakeCallback(
v8::Isolate::GetCurrent(), target, func, argc, argv,
static_cast<node::async_context>(asyncContext));
#endif
}

inline MaybeLocal<v8::Value> MakeCallback(
v8::Local<v8::Object> target
, v8::Local<v8::String> symbol
, int argc
, v8::Local<v8::Value>* argv
, async_context asyncContext) {
#if NODE_MODULE_VERSION < NODE_8_0_MODULE_VERSION
// Ignore the async_context value.
return MakeCallback(target, symbol, argc, argv);
#else
return node::MakeCallback(
v8::Isolate::GetCurrent(), target, symbol, argc, argv,
static_cast<node::async_context>(asyncContext));
#endif
}

inline MaybeLocal<v8::Value> MakeCallback(
v8::Local<v8::Object> target
, const char* method
, int argc
, v8::Local<v8::Value>* argv
, async_context asyncContext) {
#if NODE_MODULE_VERSION < NODE_8_0_MODULE_VERSION
// Ignore the async_context value.
return MakeCallback(target, method, argc, argv);
#else
return node::MakeCallback(
v8::Isolate::GetCurrent(), target, method, argc, argv,
static_cast<node::async_context>(asyncContext));
#endif
}

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 @@ -90,6 +90,10 @@
"target_name" : "makecallback"
, "sources" : [ "cpp/makecallback.cpp" ]
}
, {
"target_name" : "makecallbackcontext"
, "sources" : [ "cpp/makecallbackcontext.cpp" ]
}
, {
"target_name" : "isolatedata"
, "sources" : [ "cpp/isolatedata.cpp" ]
Expand Down
65 changes: 65 additions & 0 deletions test/cpp/makecallbackcontext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*********************************************************************
* 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:
DelayRequest(int milliseconds_, v8::Local<v8::Function> callback_)
: milliseconds(milliseconds_) {
callback.Reset(callback_);
request.data = this;
asyncContext = AsyncInit(MaybeLocal<v8::Object>(), "test.DelayRequest");
}
~DelayRequest() {
AsyncDestroy(asyncContext);
callback.Reset();
}

Persistent<v8::Function> callback;
uv_work_t request;
async_context asyncContext;
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>();
MakeCallback(target, callback, 0, argv, delay_request->asyncContext);

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, AfterDelay);
}

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

NODE_MODULE(makecallbackcontext, Init)
70 changes: 70 additions & 0 deletions test/js/makecallbackcontext-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: 'makecallbackcontext' }).delay
, asyncHooks = require('async_hooks');

test('makecallbackcontext', 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 === '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);
});
});

0 comments on commit 8b810a2

Please sign in to comment.