diff --git a/doc/api/addons.md b/doc/api/addons.md index 578b1a0f90b4ce..9ea0a6b6446ce4 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -98,6 +98,140 @@ the `.node` suffix). In the `hello.cc` example, then, the initialization function is `Initialize` and the addon module name is `addon`. +When building addons with `node-gyp`, using the macro `NODE_GYP_MODULE_NAME` as +the first parameter of `NODE_MODULE()` will ensure that the name of the final +binary will be passed to `NODE_MODULE()`. + +### Context-aware addons + +There are environments in which Node.js addons may need to be loaded multiple +times in multiple contexts. For example, the [Electron][] runtime runs multiple +instances of Node.js in a single process. Each instance will have its own +`require()` cache, and thus each instance will need a native addon to behave +correctly when loaded via `require()`. From the addon's perspective, this means +that it must support multiple initializations. + +A context-aware addon can be constructed by using the macro +`NODE_MODULE_INITIALIZER`, which expands to the name of a function which Node.js +will expect to find when it loads an addon. An addon can thus be initialized as +in the following example: + +```cpp +using namespace v8; + +extern "C" NODE_MODULE_EXPORT void +NODE_MODULE_INITIALIZER(Local exports, + Local module, + Local context) { + /* Perform addon initialization steps here. */ +} +``` + +Another option is to use the macro `NODE_MODULE_INIT()`, which will also +construct a context-aware addon. Unlike `NODE_MODULE()`, which is used to +construct an addon around a given addon initializer function, +`NODE_MODULE_INIT()` serves as the declaration of such an initializer to be +followed by a function body. + +The following three variables may be used inside the function body following an +invocation of `NODE_MODULE_INIT()`: +* `Local exports`, +* `Local module`, and +* `Local context` + +The choice to build a context-aware addon carries with it the responsibility of +carefully managing global static data. Since the addon may be loaded multiple +times, potentially even from different threads, any global static data stored +in the addon must be properly protected, and must not contain any persistent +references to JavaScript objects. The reason for this is that JavaScript +objects are only valid in one context, and will likely cause a crash when +accessed from the wrong context or from a different thread than the one on which +they were created. + +The context-aware addon can be structured to avoid global static data by +performing the following steps: +* defining a class which will hold per-addon-instance data. Such +a class should include a `v8::Persistent` which will hold a weak +reference to the addon's `exports` object. The callback associated with the weak +reference will then destroy the instance of the class. +* constructing an instance of this class in the addon initializer such that the +`v8::Persistent` is set to the `exports` object. +* storing the instance of the class in a `v8::External`, and +* passing the `v8::External` to all methods exposed to JavaScript by passing it +to the `v8::FunctionTemplate` constructor which creates the native-backed +JavaScript functions. The `v8::FunctionTemplate` constructor's third parameter +accepts the `v8::External`. + +This will ensure that the per-addon-instance data reaches each binding that can +be called from JavaScript. The per-addon-instance data must also be passed into +any asynchronous callbacks the addon may create. + +The following example illustrates the implementation of a context-aware addon: + +```cpp +#include + +using namespace v8; + +class AddonData { + public: + AddonData(Isolate* isolate, Local exports): + call_count(0) { + // Link the existence of this object instance to the existence of exports. + exports_.Reset(isolate, exports); + exports_.SetWeak(this, DeleteMe, WeakCallbackType::kParameter); + } + + ~AddonData() { + if (!exports_.IsEmpty()) { + // Reset the reference to avoid leaking data. + exports_.ClearWeak(); + exports_.Reset(); + } + } + + // Per-addon data. + int call_count; + + private: + // Method to call when "exports" is about to be garbage-collected. + static void DeleteMe(const WeakCallbackInfo& info) { + delete info.GetParameter(); + } + + // Weak handle to the "exports" object. An instance of this class will be + // destroyed along with the exports object to which it is weakly bound. + v8::Persistent exports_; +}; + +static void Method(const v8::FunctionCallbackInfo& info) { + // Retrieve the per-addon-instance data. + AddonData* data = + reinterpret_cast(info.Data().As()->Value()); + data->call_count++; + info.GetReturnValue().Set((double)data->call_count); +} + +// Initialize this addon to be context-aware. +NODE_MODULE_INIT(/* exports, module, context */) { + Isolate* isolate = context->GetIsolate(); + + // Create a new instance of AddonData for this instance of the addon. + AddonData* data = new AddonData(isolate, exports); + // Wrap the data in a v8::External so we can pass it to the method we expose. + Local external = External::New(isolate, data); + + // Expose the method "Method" to JavaScript, and make sure it receives the + // per-addon-instance data we created above by passing `external` as the + // third parameter to the FunctionTemplate constructor. + exports->Set(context, + String::NewFromUtf8(isolate, "method", NewStringType::kNormal) + .ToLocalChecked(), + FunctionTemplate::New(isolate, Method, external) + ->GetFunction(context).ToLocalChecked()).FromJust(); +} +``` + ### Building Once the source code has been written, it must be compiled into the binary @@ -1162,6 +1296,7 @@ Test in JavaScript by running: require('./build/Release/addon'); ``` +[Electron]: https://electronjs.org/ [Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide [Linking to Node.js' own dependencies]: #addons_linking_to_node_js_own_dependencies [Native Abstractions for Node.js]: https://github.com/nodejs/nan diff --git a/src/node.h b/src/node.h index b551c8bb09baf9..ed2c074b922687 100644 --- a/src/node.h +++ b/src/node.h @@ -579,6 +579,28 @@ extern "C" NODE_EXTERN void node_module_register(void* mod); */ #define NODE_MODULE_DECL /* nothing */ +#define NODE_MODULE_INITIALIZER_BASE node_register_module_v + +#define NODE_MODULE_INITIALIZER_X(base, version) \ + NODE_MODULE_INITIALIZER_X_HELPER(base, version) + +#define NODE_MODULE_INITIALIZER_X_HELPER(base, version) base##version + +#define NODE_MODULE_INITIALIZER \ + NODE_MODULE_INITIALIZER_X(NODE_MODULE_INITIALIZER_BASE, \ + NODE_MODULE_VERSION) + +#define NODE_MODULE_INIT() \ + extern "C" NODE_MODULE_EXPORT void \ + NODE_MODULE_INITIALIZER(v8::Local exports, \ + v8::Local module, \ + v8::Local context); \ + NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, \ + NODE_MODULE_INITIALIZER) \ + void NODE_MODULE_INITIALIZER(v8::Local exports, \ + v8::Local module, \ + v8::Local context) + /* Called after the event loop exits but before the VM is disposed. * Callbacks are run in reverse order of registration, i.e. newest first. */ diff --git a/test/addons/hello-world/binding.cc b/test/addons/hello-world/binding.cc index e267a3b2a7629a..341b58f9a640d8 100644 --- a/test/addons/hello-world/binding.cc +++ b/test/addons/hello-world/binding.cc @@ -6,13 +6,12 @@ void Method(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "world")); } -#define CONCAT(a, b) CONCAT_HELPER(a, b) -#define CONCAT_HELPER(a, b) a##b -#define INITIALIZER CONCAT(node_register_module_v, NODE_MODULE_VERSION) - -extern "C" NODE_MODULE_EXPORT void INITIALIZER(v8::Local exports, - v8::Local module, - v8::Local context) { +// Not using the full NODE_MODULE_INIT() macro here because we want to test the +// addon loader's reaction to the FakeInit() entry point below. +extern "C" NODE_MODULE_EXPORT void +NODE_MODULE_INITIALIZER(v8::Local exports, + v8::Local module, + v8::Local context) { NODE_SET_METHOD(exports, "hello", Method); }