diff --git a/doc/async_operations.md b/doc/async_operations.md index be4f401fc..8506e1639 100644 --- a/doc/async_operations.md +++ b/doc/async_operations.md @@ -27,3 +27,5 @@ other asynchronous mechanism, the following API is necessary to ensure an asynchronous operation is properly tracked by the runtime: - **[AsyncContext](async_context.md)** + +- **[CallbackScope](callback_scope.md)** diff --git a/doc/callback_scope.md b/doc/callback_scope.md new file mode 100644 index 000000000..45eaaf057 --- /dev/null +++ b/doc/callback_scope.md @@ -0,0 +1,54 @@ +# CallbackScope + +There are cases (for example, resolving promises) where it is necessary to have +the equivalent of the scope associated with a callback in place when making +certain N-API calls. + +## Methods + +### Constructor + +Creates a new callback scope on the stack. + +```cpp +Napi::CallbackScope::CallbackScope(napi_env env, napi_callback_scope scope); +``` + +- `[in] env`: The environment in which to create the `Napi::CallbackScope`. +- `[in] scope`: The pre-existing `napi_callback_scope` or `Napi::CallbackScope`. + +### Constructor + +Creates a new callback scope on the stack. + +```cpp +Napi::CallbackScope::CallbackScope(napi_env env, napi_async_context context); +``` + +- `[in] env`: The environment in which to create the `Napi::CallbackScope`. +- `[in] async_context`: The pre-existing `napi_async_context` or `Napi::AsyncContext`. + +### Destructor + +Deletes the instance of `Napi::CallbackScope` object. + +```cpp +virtual Napi::CallbackScope::~CallbackScope(); +``` + +### Env + +```cpp +Napi::Env Napi::CallbackScope::Env() const; +``` + +Returns the `Napi::Env` associated with the `Napi::CallbackScope`. + +## Operator + +```cpp +Napi::CallbackScope::operator napi_callback_scope() const; +``` + +Returns the N-API `napi_callback_scope` wrapped by the `Napi::CallbackScope` +object. This can be used to mix usage of the C N-API and node-addon-api. diff --git a/napi-inl.h b/napi-inl.h index 6d8a021c1..ff84d882e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3404,6 +3404,34 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) { return Value(_env, result); } +//////////////////////////////////////////////////////////////////////////////// +// CallbackScope class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackScope::CallbackScope( + napi_env env, napi_callback_scope scope) : _env(env), _scope(scope) { +} + +inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) + : _env(env), + _async_context(context) { + napi_status status = napi_open_callback_scope( + _env, Object::New(env), context, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline CallbackScope::~CallbackScope() { + napi_close_callback_scope(_env, _scope); +} + +inline CallbackScope::operator napi_callback_scope() const { + return _scope; +} + +inline Napi::Env CallbackScope::Env() const { + return Napi::Env(_env); +} + //////////////////////////////////////////////////////////////////////////////// // AsyncContext class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 61df5fe19..d60239f3e 100644 --- a/napi.h +++ b/napi.h @@ -1671,6 +1671,22 @@ namespace Napi { napi_escapable_handle_scope _scope; }; + class CallbackScope { + public: + CallbackScope(napi_env env, napi_callback_scope scope); + CallbackScope(napi_env env, napi_async_context context); + virtual ~CallbackScope(); + + operator napi_callback_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_async_context _async_context; + napi_callback_scope _scope; + }; + class AsyncContext { public: explicit AsyncContext(napi_env env, const char* resource_name); diff --git a/test/binding.cc b/test/binding.cc index 101ac1f85..09b4ff624 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -15,6 +15,7 @@ Object InitBasicTypesValue(Env env); Object InitBigInt(Env env); #endif Object InitBuffer(Env env); +Object InitCallbackScope(Env env); Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); Object InitError(Env env); @@ -47,6 +48,7 @@ Object Init(Env env, Object exports) { exports.Set("bigint", InitBigInt(env)); #endif exports.Set("buffer", InitBuffer(env)); + exports.Set("callbackscope", InitCallbackScope(env)); exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); exports.Set("dataview_read_write", InitDataViewReadWrite(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 417a2bb3b..dc648bd00 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -14,6 +14,7 @@ 'bigint.cc', 'binding.cc', 'buffer.cc', + 'callbackscope.cc', 'dataview/dataview.cc', 'dataview/dataview_read_write.cc', 'error.cc', diff --git a/test/callbackscope.cc b/test/callbackscope.cc new file mode 100644 index 000000000..75ac678f5 --- /dev/null +++ b/test/callbackscope.cc @@ -0,0 +1,20 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +static void RunInCallbackScope(const CallbackInfo& info) { + Function callback = info[0].As(); + AsyncContext context(info.Env(), "callback_scope_test"); + CallbackScope scope(info.Env(), context); + callback.Call({}); +} + +} // end anonymous namespace + +Object InitCallbackScope(Env env) { + Object exports = Object::New(env); + exports["runInCallbackScope"] = Function::New(env, RunInCallbackScope); + return exports; +} diff --git a/test/callbackscope.js b/test/callbackscope.js new file mode 100644 index 000000000..523bca462 --- /dev/null +++ b/test/callbackscope.js @@ -0,0 +1,48 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const common = require('./common'); + +// we only check async hooks on 8.x an higher were +// they are closer to working properly +const nodeVersion = process.versions.node.split('.')[0] +let async_hooks = undefined; +function checkAsyncHooks() { + if (nodeVersion >= 8) { + if (async_hooks == undefined) { + async_hooks = require('async_hooks'); + } + return true; + } + return false; +} + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + if (!checkAsyncHooks()) + return; + + let id; + let insideHook = false; + async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === 'callback_scope_test') { + id = asyncId; + } + }, + before(asyncId) { + if (asyncId === id) + insideHook = true; + }, + after(asyncId) { + if (asyncId === id) + insideHook = false; + } + }).enable(); + + binding.callbackscope.runInCallbackScope(function() { + assert(insideHook); + }); +} diff --git a/test/index.js b/test/index.js index 2f0673bd5..87237ff7f 100644 --- a/test/index.js +++ b/test/index.js @@ -16,6 +16,7 @@ let testModules = [ 'basic_types/value', 'bigint', 'buffer', + 'callbackscope', 'dataview/dataview', 'dataview/dataview_read_write', 'error',