From 602da6492f278c1345f7f2d82014d9248254476b Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 13 Jun 2018 14:01:33 -0400 Subject: src: add context-aware init macro and doc Introduces macros `NODE_MODULE_INITIALIZER` which expands to the name of the special symbol that process.dlopen() will look for to initialize an addon, and `NODE_MODULE_INIT()` which creates the boilerplate for a context-aware module which can be loaded multiple times via the special symbol mechanism. Additionally, provides an example of using the new macro to construct an addon which stores per-addon-instance data in a heap-allocated structure that gets passed to each binding, rather than in a collection of global static variables. Re: https://github.com/nodejs/node/issues/21291#issuecomment-396729727 PR-URL: https://github.com/nodejs/node/pull/21318 Reviewed-By: Matteo Collina Reviewed-By: James M Snell Reviewed-By: Ujjwal Sharma --- doc/api/addons.md | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) (limited to 'doc/api/addons.md') diff --git a/doc/api/addons.md b/doc/api/addons.md index 578b1a0f90..9ea0a6b644 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 -- cgit v1.2.3