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 <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com>
This commit is contained in:
parent
a15ea5d7ca
commit
602da6492f
@ -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<Object> exports,
|
||||
Local<Value> module,
|
||||
Local<Context> 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<Object> exports`,
|
||||
* `Local<Value> module`, and
|
||||
* `Local<Context> 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<v8::Object>` 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<v8::Object>` 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 <node.h>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
class AddonData {
|
||||
public:
|
||||
AddonData(Isolate* isolate, Local<Object> 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<AddonData>& 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<v8::Object> exports_;
|
||||
};
|
||||
|
||||
static void Method(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
||||
// Retrieve the per-addon-instance data.
|
||||
AddonData* data =
|
||||
reinterpret_cast<AddonData*>(info.Data().As<External>()->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 = 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
|
||||
|
22
src/node.h
22
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<v8::Object> exports, \
|
||||
v8::Local<v8::Value> module, \
|
||||
v8::Local<v8::Context> context); \
|
||||
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, \
|
||||
NODE_MODULE_INITIALIZER) \
|
||||
void NODE_MODULE_INITIALIZER(v8::Local<v8::Object> exports, \
|
||||
v8::Local<v8::Value> module, \
|
||||
v8::Local<v8::Context> 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.
|
||||
*/
|
||||
|
@ -6,13 +6,12 @@ void Method(const v8::FunctionCallbackInfo<v8::Value>& 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<v8::Object> exports,
|
||||
v8::Local<v8::Value> module,
|
||||
v8::Local<v8::Context> 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<v8::Object> exports,
|
||||
v8::Local<v8::Value> module,
|
||||
v8::Local<v8::Context> context) {
|
||||
NODE_SET_METHOD(exports, "hello", Method);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user