domain: further abstract usage in C++

Move the majority of C++ domain-related code into JS land by introducing
a top level domain callback which handles entering & exiting the domain.

Move the rest of the domain necessities into their own file that creates
an internal binding, to avoid exposing domain-related code on the
process object.

Modify an existing test slightly to better test domain-related code.

PR-URL: https://github.com/nodejs/node/pull/18291
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anatoli Papirovski 2018-01-19 15:42:59 -05:00
parent e4743ab619
commit eeede3b19c
No known key found for this signature in database
GPG Key ID: 614E2E1ABEB4B2C0
10 changed files with 72 additions and 90 deletions

View File

@ -94,11 +94,21 @@ process.setUncaughtExceptionCaptureCallback = function(fn) {
throw err; throw err;
}; };
function topLevelDomainCallback(cb, ...args) {
const domain = this.domain;
if (domain)
domain.enter();
const ret = Reflect.apply(cb, this, args);
if (domain)
domain.exit();
return ret;
}
// It's possible to enter one domain while already inside // It's possible to enter one domain while already inside
// another one. The stack is each entered domain. // another one. The stack is each entered domain.
const stack = []; const stack = [];
exports._stack = stack; exports._stack = stack;
process._setupDomainUse(); internalBinding('domain').enable(topLevelDomainCallback);
function updateExceptionCapture() { function updateExceptionCapture() {
if (stack.every((domain) => domain.listenerCount('error') === 0)) { if (stack.every((domain) => domain.listenerCount('error') === 0)) {

View File

@ -300,6 +300,7 @@
'src/node_constants.cc', 'src/node_constants.cc',
'src/node_contextify.cc', 'src/node_contextify.cc',
'src/node_debug_options.cc', 'src/node_debug_options.cc',
'src/node_domain.cc',
'src/node_file.cc', 'src/node_file.cc',
'src/node_http2.cc', 'src/node_http2.cc',
'src/node_http_parser.cc', 'src/node_http_parser.cc',

View File

@ -310,7 +310,6 @@ inline Environment::Environment(IsolateData* isolate_data,
immediate_info_(context->GetIsolate()), immediate_info_(context->GetIsolate()),
tick_info_(context->GetIsolate()), tick_info_(context->GetIsolate()),
timer_base_(uv_now(isolate_data->event_loop())), timer_base_(uv_now(isolate_data->event_loop())),
using_domains_(false),
printed_error_(false), printed_error_(false),
trace_sync_io_(false), trace_sync_io_(false),
abort_on_uncaught_exception_(false), abort_on_uncaught_exception_(false),
@ -426,14 +425,6 @@ inline uint64_t Environment::timer_base() const {
return timer_base_; return timer_base_;
} }
inline bool Environment::using_domains() const {
return using_domains_;
}
inline void Environment::set_using_domains(bool value) {
using_domains_ = value;
}
inline bool Environment::printed_error() const { inline bool Environment::printed_error() const {
return printed_error_; return printed_error_;
} }

View File

@ -91,7 +91,6 @@ class ModuleWrap;
V(decorated_private_symbol, "node:decorated") \ V(decorated_private_symbol, "node:decorated") \
V(npn_buffer_private_symbol, "node:npnBuffer") \ V(npn_buffer_private_symbol, "node:npnBuffer") \
V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") \ V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") \
V(domain_private_symbol, "node:domain") \
// Strings are per-isolate primitives but Environment proxies them // Strings are per-isolate primitives but Environment proxies them
// for the sake of convenience. Strings should be ASCII-only. // for the sake of convenience. Strings should be ASCII-only.
@ -128,7 +127,6 @@ class ModuleWrap;
V(dns_soa_string, "SOA") \ V(dns_soa_string, "SOA") \
V(dns_srv_string, "SRV") \ V(dns_srv_string, "SRV") \
V(dns_txt_string, "TXT") \ V(dns_txt_string, "TXT") \
V(domain_string, "domain") \
V(emit_warning_string, "emitWarning") \ V(emit_warning_string, "emitWarning") \
V(exchange_string, "exchange") \ V(exchange_string, "exchange") \
V(encoding_string, "encoding") \ V(encoding_string, "encoding") \
@ -280,6 +278,7 @@ class ModuleWrap;
V(internal_binding_cache_object, v8::Object) \ V(internal_binding_cache_object, v8::Object) \
V(buffer_prototype_object, v8::Object) \ V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \ V(context, v8::Context) \
V(domain_callback, v8::Function) \
V(host_import_module_dynamically_callback, v8::Function) \ V(host_import_module_dynamically_callback, v8::Function) \
V(http2ping_constructor_template, v8::ObjectTemplate) \ V(http2ping_constructor_template, v8::ObjectTemplate) \
V(http2stream_constructor_template, v8::ObjectTemplate) \ V(http2stream_constructor_template, v8::ObjectTemplate) \
@ -567,9 +566,6 @@ class Environment {
inline IsolateData* isolate_data() const; inline IsolateData* isolate_data() const;
inline bool using_domains() const;
inline void set_using_domains(bool value);
inline bool printed_error() const; inline bool printed_error() const;
inline void set_printed_error(bool value); inline void set_printed_error(bool value);
@ -745,7 +741,6 @@ class Environment {
ImmediateInfo immediate_info_; ImmediateInfo immediate_info_;
TickInfo tick_info_; TickInfo tick_info_;
const uint64_t timer_base_; const uint64_t timer_base_;
bool using_domains_;
bool printed_error_; bool printed_error_;
bool trace_sync_io_; bool trace_sync_io_;
bool abort_on_uncaught_exception_; bool abort_on_uncaught_exception_;

View File

@ -790,62 +790,6 @@ bool ShouldAbortOnUncaughtException(Isolate* isolate) {
} }
Local<Value> GetDomainProperty(Environment* env, Local<Object> object) {
Local<Value> domain_v =
object->GetPrivate(env->context(), env->domain_private_symbol())
.ToLocalChecked();
if (domain_v->IsObject()) {
return domain_v;
}
return object->Get(env->context(), env->domain_string()).ToLocalChecked();
}
void DomainEnter(Environment* env, Local<Object> object) {
Local<Value> domain_v = GetDomainProperty(env, object);
if (domain_v->IsObject()) {
Local<Object> domain = domain_v.As<Object>();
Local<Value> enter_v = domain->Get(env->enter_string());
if (enter_v->IsFunction()) {
if (enter_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
FatalError("node::AsyncWrap::MakeCallback",
"domain enter callback threw, please report this");
}
}
}
}
void DomainExit(Environment* env, v8::Local<v8::Object> object) {
Local<Value> domain_v = GetDomainProperty(env, object);
if (domain_v->IsObject()) {
Local<Object> domain = domain_v.As<Object>();
Local<Value> exit_v = domain->Get(env->exit_string());
if (exit_v->IsFunction()) {
if (exit_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
FatalError("node::AsyncWrap::MakeCallback",
"domain exit callback threw, please report this");
}
}
}
}
void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (env->using_domains())
return;
env->set_using_domains(true);
HandleScope scope(env->isolate());
// Do a little housekeeping.
env->process_object()->Delete(
env->context(),
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupDomainUse")).FromJust();
}
void RunMicrotasks(const FunctionCallbackInfo<Value>& args) { void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
args.GetIsolate()->RunMicrotasks(); args.GetIsolate()->RunMicrotasks();
} }
@ -982,11 +926,6 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
// If you hit this assertion, you forgot to enter the v8::Context first. // If you hit this assertion, you forgot to enter the v8::Context first.
CHECK_EQ(Environment::GetCurrent(env->isolate()), env); CHECK_EQ(Environment::GetCurrent(env->isolate()), env);
if (asyncContext.async_id == 0 && env->using_domains() &&
!object_.IsEmpty()) {
DomainEnter(env, object_);
}
if (asyncContext.async_id != 0) { if (asyncContext.async_id != 0) {
// No need to check a return value because the application will exit if // No need to check a return value because the application will exit if
// an exception occurs. // an exception occurs.
@ -1016,11 +955,6 @@ void InternalCallbackScope::Close() {
AsyncWrap::EmitAfter(env_, async_context_.async_id); AsyncWrap::EmitAfter(env_, async_context_.async_id);
} }
if (async_context_.async_id == 0 && env_->using_domains() &&
!object_.IsEmpty()) {
DomainExit(env_, object_);
}
if (IsInnerMakeCallback()) { if (IsInnerMakeCallback()) {
return; return;
} }
@ -1061,7 +995,16 @@ MaybeLocal<Value> InternalMakeCallback(Environment* env,
return Undefined(env->isolate()); return Undefined(env->isolate());
} }
MaybeLocal<Value> ret = callback->Call(env->context(), recv, argc, argv); Local<Function> domain_cb = env->domain_callback();
MaybeLocal<Value> ret;
if (asyncContext.async_id != 0 || domain_cb.IsEmpty() || recv.IsEmpty()) {
ret = callback->Call(env->context(), recv, argc, argv);
} else {
std::vector<Local<Value>> args(1 + argc);
args[0] = callback;
std::copy(&argv[0], &argv[argc], &args[1]);
ret = domain_cb->Call(env->context(), recv, args.size(), &args[0]);
}
if (ret.IsEmpty()) { if (ret.IsEmpty()) {
// NOTE: For backwards compatibility with public API we return Undefined() // NOTE: For backwards compatibility with public API we return Undefined()
@ -3339,7 +3282,6 @@ void SetupProcessObject(Environment* env,
env->SetMethod(process, "_setupProcessObject", SetupProcessObject); env->SetMethod(process, "_setupProcessObject", SetupProcessObject);
env->SetMethod(process, "_setupNextTick", SetupNextTick); env->SetMethod(process, "_setupNextTick", SetupNextTick);
env->SetMethod(process, "_setupPromises", SetupPromises); env->SetMethod(process, "_setupPromises", SetupPromises);
env->SetMethod(process, "_setupDomainUse", SetupDomainUse);
} }

34
src/node_domain.cc Normal file
View File

@ -0,0 +1,34 @@
#include "v8.h"
#include "node_internals.h"
namespace node {
namespace domain {
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::Object;
using v8::Value;
void Enable(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsFunction());
env->set_domain_callback(args[0].As<Function>());
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
Environment* env = Environment::GetCurrent(context);
env->SetMethod(target, "enable", Enable);
}
} // namespace domain
} // namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(domain, node::domain::Initialize)

View File

@ -104,6 +104,7 @@ struct sockaddr;
V(cares_wrap) \ V(cares_wrap) \
V(config) \ V(config) \
V(contextify) \ V(contextify) \
V(domain) \
V(fs) \ V(fs) \
V(fs_event_wrap) \ V(fs_event_wrap) \
V(http2) \ V(http2) \

View File

@ -22,6 +22,7 @@
#include <node.h> #include <node.h>
#include <v8.h> #include <v8.h>
using v8::Boolean;
using v8::Function; using v8::Function;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
using v8::Local; using v8::Local;
@ -31,11 +32,16 @@ using v8::Value;
void Method(const FunctionCallbackInfo<Value>& args) { void Method(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate(); Isolate* isolate = args.GetIsolate();
node::MakeCallback(isolate, Local<Value> params[] = {
Boolean::New(isolate, true),
Boolean::New(isolate, false)
};
Local<Value> ret = node::MakeCallback(isolate,
isolate->GetCurrentContext()->Global(), isolate->GetCurrentContext()->Global(),
args[0].As<Function>(), args[0].As<Function>(),
0, 2,
nullptr); params);
assert(ret->IsTrue());
} }
void init(Local<Object> exports) { void init(Local<Object> exports) {

View File

@ -40,7 +40,8 @@ const lines = [
// This line shouldn't cause an assertion error. // This line shouldn't cause an assertion error.
`require('${buildPath}')` + `require('${buildPath}')` +
// Log output to double check callback ran. // Log output to double check callback ran.
'.method(function() { console.log(\'cb_ran\'); });', '.method(function(v1, v2) {' +
'console.log(\'cb_ran\'); return v1 === true && v2 === false; });',
]; ];
const dInput = new stream.Readable(); const dInput = new stream.Readable();

View File

@ -5,6 +5,7 @@
void _register_cares_wrap() {} void _register_cares_wrap() {}
void _register_config() {} void _register_config() {}
void _register_contextify() {} void _register_contextify() {}
void _register_domain() {}
void _register_fs() {} void _register_fs() {}
void _register_fs_event_wrap() {} void _register_fs_event_wrap() {}
void _register_http2() {} void _register_http2() {}