diff --git a/doc/api/addons.md b/doc/api/addons.md index d993c72d346495..9bc75c7dfc15d1 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -234,6 +234,23 @@ NODE_MODULE_INIT(/* exports, module, context */) { } ``` +#### Worker support + +In order to support [`Worker`][] threads, addons need to clean up any resources +they may have allocated when such a thread exists. This can be achieved through +the usage of the `AddEnvironmentCleanupHook()` function: + +```c++ +void AddEnvironmentCleanupHook(v8::Isolate* isolate, + void (*fun)(void* arg), + void* arg); +``` + +This function adds a hook that will run before a given Node.js instance shuts +down. If necessary, such hooks can be removed using +`RemoveEnvironmentCleanupHook()` before they are run, which has the same +signature. + ### Building Once the source code has been written, it must be compiled into the binary @@ -1349,6 +1366,7 @@ Test in JavaScript by running: require('./build/Release/addon'); ``` +[`Worker`]: worker_threads.html#worker_threads_class_worker [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 diff --git a/test/addons/hello-world/test-worker.js b/test/addons/hello-world/test-worker.js new file mode 100644 index 00000000000000..f989c738c873c7 --- /dev/null +++ b/test/addons/hello-world/test-worker.js @@ -0,0 +1,14 @@ +// Flags: --experimental-worker +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const path = require('path'); +const { Worker } = require('worker_threads'); +const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`); + +const w = new Worker(` +require('worker_threads').parentPort.postMessage( + require(${JSON.stringify(binding)}).hello());`, { eval: true }); +w.on('message', common.mustCall((message) => { + assert.strictEqual(message, 'world'); +})); diff --git a/test/addons/worker-addon/binding.cc b/test/addons/worker-addon/binding.cc new file mode 100644 index 00000000000000..1fb85ae230eb5f --- /dev/null +++ b/test/addons/worker-addon/binding.cc @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include + +using v8::Context; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::Value; + +size_t count = 0; + +struct statically_allocated { + statically_allocated() { + assert(count == 0); + printf("ctor "); + } + ~statically_allocated() { + assert(count == 0); + printf("dtor"); + } +} var; + +void Dummy(void*) { + assert(0); +} + +void Cleanup(void* str) { + printf("%s ", static_cast(str)); +} + +void Initialize(Local exports, + Local module, + Local context) { + node::AddEnvironmentCleanupHook( + context->GetIsolate(), + Cleanup, + const_cast(static_cast("cleanup"))); + node::AddEnvironmentCleanupHook(context->GetIsolate(), Dummy, nullptr); + node::RemoveEnvironmentCleanupHook(context->GetIsolate(), Dummy, nullptr); +} + +NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize) diff --git a/test/addons/worker-addon/binding.gyp b/test/addons/worker-addon/binding.gyp new file mode 100644 index 00000000000000..7ede63d94a0d77 --- /dev/null +++ b/test/addons/worker-addon/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons/worker-addon/test.js b/test/addons/worker-addon/test.js new file mode 100644 index 00000000000000..2f4bb512fc6571 --- /dev/null +++ b/test/addons/worker-addon/test.js @@ -0,0 +1,21 @@ +// Flags: --experimental-worker +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const child_process = require('child_process'); +const path = require('path'); +const { Worker } = require('worker_threads'); +const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`); + +if (process.argv[2] === 'child') { + new Worker(`require(${JSON.stringify(binding)});`, { eval: true }); +} else { + const proc = child_process.spawnSync(process.execPath, [ + '--experimental-worker', + __filename, + 'child' + ]); + assert.strictEqual(proc.stderr.toString(), ''); + assert.strictEqual(proc.stdout.toString(), 'ctor cleanup dtor'); + assert.strictEqual(proc.status, 0); +}