aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/abort/test-addon-uv-handle-leak.js97
-rw-r--r--test/addons/uv-handle-leak/binding.cc48
-rw-r--r--test/addons/uv-handle-leak/binding.gyp9
-rw-r--r--test/addons/uv-handle-leak/test.js23
-rw-r--r--test/common/index.js17
5 files changed, 194 insertions, 0 deletions
diff --git a/test/abort/test-addon-uv-handle-leak.js b/test/abort/test-addon-uv-handle-leak.js
new file mode 100644
index 0000000000..3944cb79c7
--- /dev/null
+++ b/test/abort/test-addon-uv-handle-leak.js
@@ -0,0 +1,97 @@
+// Flags: --experimental-worker
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const cp = require('child_process');
+const { Worker } = require('worker_threads');
+
+// This is a sibling test to test/addons/uv-handle-leak.
+
+const bindingPath = path.resolve(
+ __dirname, '..', 'addons', 'uv-handle-leak', 'build',
+ `${common.buildType}/binding.node`);
+
+if (!fs.existsSync(bindingPath))
+ common.skip('binding not built yet');
+
+if (process.argv[2] === 'child') {
+ new Worker(`
+ const binding = require(${JSON.stringify(bindingPath)});
+
+ binding.leakHandle();
+ binding.leakHandle(0);
+ binding.leakHandle(0x42);
+ `, { eval: true });
+} else {
+ const child = cp.spawnSync(process.execPath,
+ ['--experimental-worker', __filename, 'child']);
+ const stderr = child.stderr.toString();
+
+ assert.strictEqual(child.stdout.toString(), '');
+
+ const lines = stderr.split('\n');
+
+ let state = 'initial';
+
+ // parse output that is formatted like this:
+
+ // uv loop at [0x559b65ed5770] has active handles
+ // [0x7f2de0018430] timer
+ // Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
+ // Data: 0x7f2df33df140 example_instance [...]
+ // (First field): 0x7f2df33dedc0 vtable for ExampleOwnerClass [...]
+ // [0x7f2de000b870] timer
+ // Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
+ // Data: (nil)
+ // [0x7f2de000b910] timer
+ // Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
+ // Data: 0x42
+
+ if (!(common.isFreeBSD ||
+ common.isAIX ||
+ (common.isLinux && !common.isGlibc()) ||
+ common.isWindows)) {
+ assert(stderr.includes('ExampleOwnerClass'), stderr);
+ assert(stderr.includes('CloseCallback'), stderr);
+ assert(stderr.includes('example_instance'), stderr);
+ }
+
+ while (lines.length > 0) {
+ const line = lines.shift().trim();
+
+ switch (state) {
+ case 'initial':
+ assert(/^uv loop at \[.+\] has active handles$/.test(line), line);
+ state = 'handle-start';
+ break;
+ case 'handle-start':
+ if (/Assertion .+ failed/.test(line)) {
+ state = 'done';
+ break;
+ }
+ assert(/^\[.+\] timer$/.test(line), line);
+ state = 'close-callback';
+ break;
+ case 'close-callback':
+ assert(/^Close callback:/.test(line), line);
+ state = 'data';
+ break;
+ case 'data':
+ assert(/^Data: .+$/.test(line), line);
+ state = 'maybe-first-field';
+ break;
+ case 'maybe-first-field':
+ if (/^\(First field\)$/.test(line)) {
+ lines.unshift(line);
+ state = 'handle-start';
+ break;
+ }
+ state = 'maybe-first-field';
+ break;
+ case 'done':
+ break;
+ }
+ }
+}
diff --git a/test/addons/uv-handle-leak/binding.cc b/test/addons/uv-handle-leak/binding.cc
new file mode 100644
index 0000000000..c2e5f0bf27
--- /dev/null
+++ b/test/addons/uv-handle-leak/binding.cc
@@ -0,0 +1,48 @@
+#include <node.h>
+#include <v8.h>
+#include <uv.h>
+
+using v8::Context;
+using v8::FunctionCallbackInfo;
+using v8::Isolate;
+using v8::Local;
+using v8::Object;
+using v8::Value;
+
+// Give these things names in the public namespace so that we can see
+// them show up in symbol dumps.
+void CloseCallback(uv_handle_t* handle) {}
+
+class ExampleOwnerClass {
+ public:
+ virtual ~ExampleOwnerClass() {}
+};
+
+ExampleOwnerClass example_instance;
+
+void LeakHandle(const FunctionCallbackInfo<Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ Local<Context> context = isolate->GetCurrentContext();
+ uv_loop_t* loop = node::GetCurrentEventLoop(isolate);
+ assert(loop != nullptr);
+
+ uv_timer_t* leaked_timer = new uv_timer_t;
+ leaked_timer->close_cb = CloseCallback;
+
+ if (args[0]->IsNumber()) {
+ leaked_timer->data =
+ reinterpret_cast<void*>(args[0]->IntegerValue(context).FromJust());
+ } else {
+ leaked_timer->data = &example_instance;
+ }
+
+ uv_timer_init(loop, leaked_timer);
+ uv_timer_start(leaked_timer, [](uv_timer_t*){}, 1000, 1000);
+ uv_unref(reinterpret_cast<uv_handle_t*>(leaked_timer));
+}
+
+void Initialize(v8::Local<v8::Object> exports) {
+ NODE_SET_METHOD(exports, "leakHandle", LeakHandle);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
diff --git a/test/addons/uv-handle-leak/binding.gyp b/test/addons/uv-handle-leak/binding.gyp
new file mode 100644
index 0000000000..7ede63d94a
--- /dev/null
+++ b/test/addons/uv-handle-leak/binding.gyp
@@ -0,0 +1,9 @@
+{
+ 'targets': [
+ {
+ 'target_name': 'binding',
+ 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
+ 'sources': [ 'binding.cc' ]
+ }
+ ]
+}
diff --git a/test/addons/uv-handle-leak/test.js b/test/addons/uv-handle-leak/test.js
new file mode 100644
index 0000000000..73d40ca799
--- /dev/null
+++ b/test/addons/uv-handle-leak/test.js
@@ -0,0 +1,23 @@
+'use strict';
+const common = require('../../common');
+const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
+const binding = require(bindingPath);
+
+// This tests checks that addons may leak libuv handles until process exit.
+// It’s really not a good idea to do so, but it tests existing behaviour
+// that likely can never be removed for backwards compatibility.
+
+// This has a sibling test in test/abort/ which checks output for failures
+// from workers.
+
+try {
+ // We don’t want to run this in Workers because they do actually enforce
+ // a clean-exit policy.
+ const { isMainThread } = require('worker_threads');
+ if (!isMainThread)
+ common.skip('Cannot run test in environment with clean-exit policy');
+} catch {}
+
+binding.leakHandle();
+binding.leakHandle(0);
+binding.leakHandle(1);
diff --git a/test/common/index.js b/test/common/index.js
index 6437b9952d..bf6b1077d1 100644
--- a/test/common/index.js
+++ b/test/common/index.js
@@ -68,6 +68,23 @@ exports.isOpenBSD = process.platform === 'openbsd';
exports.isLinux = process.platform === 'linux';
exports.isOSX = process.platform === 'darwin';
+let isGlibc;
+exports.isGlibc = () => {
+ if (isGlibc !== undefined)
+ return isGlibc;
+ try {
+ const lddOut = spawnSync('ldd', [process.execPath]).stdout;
+ const libcInfo = lddOut.toString().split('\n').map(
+ (line) => line.match(/libc\.so.+=>\s*(\S+)\s/)).filter((info) => info);
+ if (libcInfo.length === 0)
+ return isGlibc = false;
+ const nmOut = spawnSync('nm', ['-D', libcInfo[0][1]]).stdout;
+ if (/gnu_get_libc_version/.test(nmOut))
+ return isGlibc = true;
+ } catch {}
+ return isGlibc = false;
+};
+
exports.enoughTestMem = os.totalmem() > 0x70000000; /* 1.75 Gb */
const cpus = os.cpus();
exports.enoughTestCpu = Array.isArray(cpus) &&