From d3132048cbf0fcf99e99a0e9baa73e7434a4c0d0 Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Mon, 21 Mar 2016 15:59:31 -0400 Subject: [PATCH] handle_wrap: expose an `isRefed()` check to JS This allows third-party tools to check whether or not a handle that can be unreferenced is unreferenced at a particular time. Notably, this should be helpful for inspection via AsyncWrap. Also, this is useful even to node's internals, particularly timers. Refs: https://github.com/nodejs/node/pull/5828 Refs: https://github.com/nodejs/node/pull/5827 PR-URL: https://github.com/nodejs/node/pull/5834 Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Trevor Norris --- src/handle_wrap.cc | 10 ++ src/handle_wrap.h | 1 + src/pipe_wrap.cc | 1 + src/process_wrap.cc | 1 + src/signal_wrap.cc | 1 + src/tcp_wrap.cc | 1 + src/timer_wrap.cc | 1 + src/tty_wrap.cc | 1 + src/udp_wrap.cc | 1 + test/parallel/test-handle-wrap-isrefed-tty.js | 31 ++++++ test/parallel/test-handle-wrap-isrefed.js | 97 +++++++++++++++++++ 11 files changed, 146 insertions(+) create mode 100644 test/parallel/test-handle-wrap-isrefed-tty.js create mode 100644 test/parallel/test-handle-wrap-isrefed.js diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index 43c5490eefa888..9af60d50258243 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -9,6 +9,7 @@ namespace node { +using v8::Boolean; using v8::Context; using v8::FunctionCallbackInfo; using v8::HandleScope; @@ -37,6 +38,15 @@ void HandleWrap::Unref(const FunctionCallbackInfo& args) { } +void HandleWrap::IsRefed(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + HandleWrap* wrap = Unwrap(args.Holder()); + + bool refed = IsAlive(wrap) && (wrap->flags_ & kUnref) == 0; + args.GetReturnValue().Set(refed); +} + + void HandleWrap::Close(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); diff --git a/src/handle_wrap.h b/src/handle_wrap.h index da712b33befbcc..cb3ba3f5d52e15 100644 --- a/src/handle_wrap.h +++ b/src/handle_wrap.h @@ -35,6 +35,7 @@ class HandleWrap : public AsyncWrap { static void Close(const v8::FunctionCallbackInfo& args); static void Ref(const v8::FunctionCallbackInfo& args); static void Unref(const v8::FunctionCallbackInfo& args); + static void IsRefed(const v8::FunctionCallbackInfo& args); static inline bool IsAlive(const HandleWrap* wrap) { return wrap != nullptr && wrap->GetHandle() != nullptr; diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index 1c60944fe64079..25080041c276d8 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -80,6 +80,7 @@ void PipeWrap::Initialize(Local target, env->SetProtoMethod(t, "close", HandleWrap::Close); env->SetProtoMethod(t, "unref", HandleWrap::Unref); env->SetProtoMethod(t, "ref", HandleWrap::Ref); + env->SetProtoMethod(t, "isRefed", HandleWrap::IsRefed); StreamWrap::AddMethods(env, t); diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 420c71d7ea4052..0b7ad41b61a5ae 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -40,6 +40,7 @@ class ProcessWrap : public HandleWrap { env->SetProtoMethod(constructor, "ref", HandleWrap::Ref); env->SetProtoMethod(constructor, "unref", HandleWrap::Unref); + env->SetProtoMethod(constructor, "isRefed", HandleWrap::IsRefed); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Process"), constructor->GetFunction()); diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index 5f6a6a52bcd786..ca5201d81a4b14 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -32,6 +32,7 @@ class SignalWrap : public HandleWrap { env->SetProtoMethod(constructor, "close", HandleWrap::Close); env->SetProtoMethod(constructor, "ref", HandleWrap::Ref); env->SetProtoMethod(constructor, "unref", HandleWrap::Unref); + env->SetProtoMethod(constructor, "isRefed", HandleWrap::IsRefed); env->SetProtoMethod(constructor, "start", Start); env->SetProtoMethod(constructor, "stop", Stop); diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index c36e814ec6e37f..4e8617af2d0c18 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -87,6 +87,7 @@ void TCPWrap::Initialize(Local target, env->SetProtoMethod(t, "ref", HandleWrap::Ref); env->SetProtoMethod(t, "unref", HandleWrap::Unref); + env->SetProtoMethod(t, "isRefed", HandleWrap::IsRefed); StreamWrap::AddMethods(env, t, StreamBase::kFlagHasWritev); diff --git a/src/timer_wrap.cc b/src/timer_wrap.cc index 79fa86953d47e2..4a1cd3716a9030 100644 --- a/src/timer_wrap.cc +++ b/src/timer_wrap.cc @@ -39,6 +39,7 @@ class TimerWrap : public HandleWrap { env->SetProtoMethod(constructor, "close", HandleWrap::Close); env->SetProtoMethod(constructor, "ref", HandleWrap::Ref); env->SetProtoMethod(constructor, "unref", HandleWrap::Unref); + env->SetProtoMethod(constructor, "isRefed", HandleWrap::IsRefed); env->SetProtoMethod(constructor, "start", Start); env->SetProtoMethod(constructor, "stop", Stop); diff --git a/src/tty_wrap.cc b/src/tty_wrap.cc index 63e8454bacdec3..5a1d333c3d2bf1 100644 --- a/src/tty_wrap.cc +++ b/src/tty_wrap.cc @@ -36,6 +36,7 @@ void TTYWrap::Initialize(Local target, env->SetProtoMethod(t, "close", HandleWrap::Close); env->SetProtoMethod(t, "unref", HandleWrap::Unref); + env->SetProtoMethod(t, "isRefed", HandleWrap::IsRefed); StreamWrap::AddMethods(env, t, StreamBase::kFlagNoShutdown); diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index dc2804812fdf19..ac087f395aced7 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -108,6 +108,7 @@ void UDPWrap::Initialize(Local target, env->SetProtoMethod(t, "ref", HandleWrap::Ref); env->SetProtoMethod(t, "unref", HandleWrap::Unref); + env->SetProtoMethod(t, "isRefed", HandleWrap::IsRefed); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "UDP"), t->GetFunction()); env->set_udp_constructor_function(t->GetFunction()); diff --git a/test/parallel/test-handle-wrap-isrefed-tty.js b/test/parallel/test-handle-wrap-isrefed-tty.js new file mode 100644 index 00000000000000..9656fe06eee72c --- /dev/null +++ b/test/parallel/test-handle-wrap-isrefed-tty.js @@ -0,0 +1,31 @@ +'use strict'; + +const common = require('../common'); +const strictEqual = require('assert').strictEqual; +const spawn = require('child_process').spawn; + +function makeAssert(message) { + return function(actual, expected) { + strictEqual(actual, expected, message); + }; +} +const assert = makeAssert('isRefed() not working on tty_wrap'); + +if (process.argv[2] === 'child') { + // Test tty_wrap in piped child to guarentee stdin being a TTY. + const ReadStream = require('tty').ReadStream; + const tty = new ReadStream(0); + assert(Object.getPrototypeOf(tty._handle).hasOwnProperty('isRefed'), true); + assert(tty._handle.isRefed(), true); + tty.unref(); + assert(tty._handle.isRefed(), false); + return; +} + +// Use spawn so that we can be sure that stdin has a _handle property. +// Refs: https://github.com/nodejs/node/pull/5916 +const proc = spawn(process.execPath, [__filename, 'child'], { stdio: 'pipe' }); +proc.stderr.pipe(process.stderr); +proc.on('exit', common.mustCall(function(exitCode) { + process.exitCode = exitCode; +})); diff --git a/test/parallel/test-handle-wrap-isrefed.js b/test/parallel/test-handle-wrap-isrefed.js new file mode 100644 index 00000000000000..cd828aa1c4f7d5 --- /dev/null +++ b/test/parallel/test-handle-wrap-isrefed.js @@ -0,0 +1,97 @@ +'use strict'; + +const common = require('../common'); +const strictEqual = require('assert').strictEqual; + +function makeAssert(message) { + return function(actual, expected) { + strictEqual(actual, expected, message); + }; +} + + +// child_process +{ + const assert = makeAssert('isRefed() not working on process_wrap'); + const spawn = require('child_process').spawn; + const cmd = common.isWindows ? 'rundll32' : 'ls'; + const cp = spawn(cmd); + assert(Object.getPrototypeOf(cp._handle).hasOwnProperty('isRefed'), true); + assert(cp._handle.isRefed(), true); + cp.unref(); + assert(cp._handle.isRefed(), false); + cp.ref(); + assert(cp._handle.isRefed(), true); + cp.unref(); +} + + +// dgram +{ + const assert = makeAssert('isRefed() not working on udp_wrap'); + const dgram = require('dgram'); + + const sock4 = dgram.createSocket('udp4'); + assert(Object.getPrototypeOf(sock4._handle).hasOwnProperty('isRefed'), true); + assert(sock4._handle.isRefed(), true); + sock4.unref(); + assert(sock4._handle.isRefed(), false); + sock4.ref(); + assert(sock4._handle.isRefed(), true); + sock4.unref(); + + const sock6 = dgram.createSocket('udp6'); + assert(Object.getPrototypeOf(sock6._handle).hasOwnProperty('isRefed'), true); + assert(sock6._handle.isRefed(), true); + sock6.unref(); + assert(sock6._handle.isRefed(), false); + sock6.ref(); + assert(sock6._handle.isRefed(), true); + sock6.unref(); +} + + +// pipe +{ + const assert = makeAssert('isRefed() not working on pipe_wrap'); + const Pipe = process.binding('pipe_wrap').Pipe; + const handle = new Pipe(); + assert(Object.getPrototypeOf(handle).hasOwnProperty('isRefed'), true); + assert(handle.isRefed(), true); + handle.unref(); + assert(handle.isRefed(), false); + handle.ref(); + assert(handle.isRefed(), true); + handle.unref(); +} + + +// tcp +{ + const assert = makeAssert('isRefed() not working on tcp_wrap'); + const net = require('net'); + const server = net.createServer(() => {}).listen(common.PORT); + assert(Object.getPrototypeOf(server._handle).hasOwnProperty('isRefed'), true); + assert(server._handle.isRefed(), true); + assert(server._unref, false); + server.unref(); + assert(server._handle.isRefed(), false); + assert(server._unref, true); + server.ref(); + assert(server._handle.isRefed(), true); + assert(server._unref, false); + server.unref(); +} + + +// timers +{ + const assert = makeAssert('isRefed() not working on timer_wrap'); + const timer = setTimeout(() => {}, 500); + timer.unref(); + assert(Object.getPrototypeOf(timer._handle).hasOwnProperty('isRefed'), true); + assert(timer._handle.isRefed(), false); + timer.ref(); + assert(timer._handle.isRefed(), true); + timer.unref(); +}