diff --git a/LICENSE b/LICENSE index f527a1c8b23124..e5fa1f5acd9dc8 100644 --- a/LICENSE +++ b/LICENSE @@ -1056,3 +1056,35 @@ The externally maintained libraries used by Node.js are: ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ + +- ProducerConsumerQueue, located at src/producer-consumer-queue.h. The folly::ProducerConsumerQueue class is a + one-producer one-consumer queue with very low synchronization overhead. + ProducerConsumerQueue's license follows: + """ + Copyright 2015 Facebook, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + """ + + Significant changes made to the software: + + - Removed Boost dependency + - Removed support for storing values directly + - Removed construction and destruction of the queue items feature + - Added initialization of all values to nullptr + - Made size a template parameter + - Crash instead of throw if malloc fails in constructor + - Changed namespace from folly to node + - Removed sizeGuess(), isFull(), isEmpty(), popFront() and frontPtr() methods + - Renamed write() to PushBack(), read() to PopFront() + - Added padding to fields diff --git a/Makefile b/Makefile index bcb434d6a5576e..4ada402fffe9be 100644 --- a/Makefile +++ b/Makefile @@ -231,6 +231,12 @@ test-v8-benchmarks: test-v8-all: test-v8 test-v8-intl test-v8-benchmarks # runs all v8 tests +test-workers: all + $(PYTHON) tools/test.py --mode=release workers -J + +test-workers-debug: all + $(PYTHON) tools/test.py --mode=debug workers -J + apidoc_sources = $(wildcard doc/api/*.markdown) apidocs = $(addprefix out/,$(apidoc_sources:.markdown=.html)) \ $(addprefix out/,$(apidoc_sources:.markdown=.json)) diff --git a/common.gypi b/common.gypi index 5b8b2c09d6b4a9..efc8bf24fa5428 100644 --- a/common.gypi +++ b/common.gypi @@ -279,7 +279,7 @@ 'libraries': [ '-llog' ], }], ['OS=="mac"', { - 'defines': ['_DARWIN_USE_64_BIT_INODE=1'], + 'defines': ['_DARWIN_USE_64_BIT_INODE=1', 'NODE_OS_MACOSX'], 'xcode_settings': { 'ALWAYS_SEARCH_USER_PATHS': 'NO', 'GCC_CW_ASM_SYNTAX': 'NO', # No -fasm-blocks @@ -290,7 +290,7 @@ 'GCC_ENABLE_PASCAL_STRINGS': 'NO', # No -mpascal-strings 'GCC_THREADSAFE_STATICS': 'NO', # -fno-threadsafe-statics 'PREBINDING': 'NO', # No -Wl,-prebind - 'MACOSX_DEPLOYMENT_TARGET': '10.5', # -mmacosx-version-min=10.5 + 'MACOSX_DEPLOYMENT_TARGET': '10.7', # -mmacosx-version-min=10.7 'USE_HEADERMAP': 'NO', 'OTHER_CFLAGS': [ '-fno-strict-aliasing', @@ -317,7 +317,8 @@ ['clang==1', { 'xcode_settings': { 'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0', - 'CLANG_CXX_LANGUAGE_STANDARD': 'gnu++0x', # -std=gnu++0x + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', # -std=c++11 + 'CLANG_CXX_LIBRARY': 'libc++', #-stdlib=libc++ }, }], ], diff --git a/lib/worker.js b/lib/worker.js new file mode 100644 index 00000000000000..21a3af5e4a47ac --- /dev/null +++ b/lib/worker.js @@ -0,0 +1,216 @@ +'use strict'; + +if (!process.features.experimental_workers) { + throw new Error('Experimental workers are not enabled'); +} + +const util = require('util'); +const assert = require('assert'); +const EventEmitter = require('events'); +const WorkerContextBinding = process.binding('worker_context'); +const JSONStringify = function(value) { + if (value === undefined) value = null; + return JSON.stringify(value); +}; +const JSONParse = JSON.parse; +const EMPTY_ARRAY = []; + +const workerContextSymbol = Symbol('workerContext'); +const installEventsSymbol = Symbol('installEvents'); +const checkAliveSymbol = Symbol('checkAlive'); +const initSymbol = WorkerContextBinding.initSymbol; + +const builtinErrorTypes = new Map([ + Error, SyntaxError, RangeError, URIError, TypeError, EvalError, ReferenceError +].map(function(Type) { + return [Type.name, Type]; +})); + +const Worker = WorkerContextBinding.JsConstructor; +util.inherits(Worker, EventEmitter); + +Worker.prototype[initSymbol] = function(entryModulePath, options) { + if (typeof entryModulePath !== 'string') + throw new TypeError('entryModulePath must be a string'); + EventEmitter.call(this); + options = Object(options); + const keepAlive = + options.keepAlive === undefined ? true : !!options.keepAlive; + const evalCode = !!options.eval; + const userData = JSONStringify(options.data); + this[workerContextSymbol] = + new WorkerContextBinding.WorkerContext(entryModulePath, { + keepAlive: keepAlive, + userData: userData, + eval: evalCode + }); + this[installEventsSymbol](); +}; + +Worker.prototype[installEventsSymbol] = function() { + const workerObject = this; + const workerContext = this[workerContextSymbol]; + + const onerror = function(payload) { + var ErrorConstructor = builtinErrorTypes.get(payload.builtinType); + if (typeof ErrorConstructor !== 'function') + ErrorConstructor = Error; + const error = new ErrorConstructor(payload.message); + error.stack = payload.stack; + util._extend(error, payload.additionalProperties); + workerObject.emit('error', error); + }; + + workerContext._onexit = function(exitCode) { + workerObject[workerContextSymbol] = null; + workerObject.emit('exit', exitCode); + }; + + workerContext._onmessage = function(payload, messageType) { + payload = JSONParse(payload); + switch (messageType) { + case WorkerContextBinding.USER: + return workerObject.emit('message', payload); + case WorkerContextBinding.EXCEPTION: + return onerror(payload); + default: + assert.fail('unreachable'); + } + }; +}; + +Worker.prototype[checkAliveSymbol] = function() { + if (!this[workerContextSymbol]) + throw new RangeError('this worker has been terminated'); +}; + +Worker.prototype.postMessage = function(payload) { + this[checkAliveSymbol](); + this[workerContextSymbol].postMessage(JSONStringify(payload), + EMPTY_ARRAY, + WorkerContextBinding.USER); +}; + +Worker.prototype.terminate = function(callback) { + this[checkAliveSymbol](); + const context = this[workerContextSymbol]; + this[workerContextSymbol] = null; + if (typeof callback === 'function') { + this.once('exit', function(exitCode) { + callback(null, exitCode); + }); + } + context.terminate(); +}; + +Worker.prototype.ref = function() { + this[checkAliveSymbol](); + this[workerContextSymbol].ref(); +}; + +Worker.prototype.unref = function() { + this[checkAliveSymbol](); + this[workerContextSymbol].unref(); +}; + +if (process.isWorkerThread) { + const postMessage = function(payload, transferList, type) { + if (!Array.isArray(transferList)) + throw new TypeError('transferList must be an array'); + + WorkerContextBinding.workerWrapper._postMessage(JSONStringify(payload), + transferList, + type); + }; + const workerFatalError = function(er) { + const defaultStack = null; + const defaultMessage = '[toString() conversion failed]'; + const defaultBuiltinType = 'Error'; + + var message = defaultMessage; + var builtinType = defaultBuiltinType; + var stack = defaultStack; + var additionalProperties = {}; + + // As this is a fatal error handler we cannot throw any new errors here + // but should still try to extract as much info as possible from the + // cause. + if (er instanceof Error) { + // .name can be a getter that throws. + try { + builtinType = er.name; + } catch (ignore) {} + + if (typeof builtinType !== 'string') + builtinType = defaultBuiltinType; + + // .stack can be a getter that throws. + try { + stack = er.stack; + } catch (ignore) {} + + if (typeof stack !== 'string') + stack = defaultStack; + + try { + // Get inherited enumerable properties. + // .name, .stack and .message are all non-enumerable + for (var key in er) + additionalProperties[key] = er[key]; + // The message delivery must always succeed, otherwise the real cause + // of this fatal error is masked. + JSONStringify(additionalProperties); + } catch (e) { + additionalProperties = {}; + } + } + + try { + // .message can be a getter that throws or the call to toString may fail. + if (er instanceof Error) { + message = er.message; + if (typeof message !== 'string') + message = '' + er; + } else { + message = '' + er; + } + } catch (e) { + message = defaultMessage; + } + + postMessage({ + message: message, + stack: stack, + additionalProperties: additionalProperties, + builtinType: builtinType + }, EMPTY_ARRAY, WorkerContextBinding.EXCEPTION); + }; + + util._extend(Worker, EventEmitter.prototype); + EventEmitter.call(Worker); + + WorkerContextBinding.workerWrapper._onmessage = + function(payload, messageType) { + payload = JSONParse(payload); + switch (messageType) { + case WorkerContextBinding.USER: + return Worker.emit('message', payload); + default: + assert.fail('unreachable'); + } + }; + + Worker.postMessage = function(payload) { + postMessage(payload, EMPTY_ARRAY, WorkerContextBinding.USER); + }; + + Object.defineProperty(Worker, '_workerFatalError', { + configurable: true, + writable: false, + enumerable: false, + value: workerFatalError + }); +} + + +module.exports = Worker; diff --git a/node.gyp b/node.gyp index dc525e1421333d..c6148ff0fe252b 100644 --- a/node.gyp +++ b/node.gyp @@ -69,6 +69,7 @@ 'lib/v8.js', 'lib/vm.js', 'lib/zlib.js', + 'lib/worker.js', 'lib/internal/child_process.js', 'lib/internal/cluster.js', 'lib/internal/freelist.js', @@ -139,6 +140,8 @@ 'src/node_watchdog.cc', 'src/node_zlib.cc', 'src/node_i18n.cc', + 'src/notification-channel.cc', + 'src/persistent-handle-cleanup.cc', 'src/pipe_wrap.cc', 'src/signal_wrap.cc', 'src/spawn_sync.cc', @@ -151,6 +154,7 @@ 'src/process_wrap.cc', 'src/udp_wrap.cc', 'src/uv.cc', + 'src/worker.cc', # headers to make for a more pleasant IDE experience 'src/async-wrap.h', 'src/async-wrap-inl.h', @@ -164,6 +168,7 @@ 'src/node.h', 'src/node_buffer.h', 'src/node_constants.h', + 'src/node-contextify.h', 'src/node_file.h', 'src/node_http_parser.h', 'src/node_internals.h', @@ -174,7 +179,10 @@ 'src/node_wrap.h', 'src/node_revert.h', 'src/node_i18n.h', + 'src/notification-channel.h', + 'src/persistent-handle-cleanup.h', 'src/pipe_wrap.h', + 'src/producer-consumer-queue.h', 'src/tty_wrap.h', 'src/tcp_wrap.h', 'src/udp_wrap.h', @@ -189,6 +197,7 @@ 'src/util-inl.h', 'src/util.cc', 'src/string_search.cc', + 'src/worker.h', 'deps/http_parser/http_parser.h', 'deps/v8/include/v8.h', 'deps/v8/include/v8-debug.h', @@ -207,6 +216,11 @@ 'V8_DEPRECATION_WARNINGS=1', ], + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '-stdlib=libc++', + ], + }, 'conditions': [ [ 'node_tag!=""', { @@ -679,7 +693,8 @@ 'dependencies': [ 'deps/gtest/gtest.gyp:gtest', 'deps/v8/tools/gyp/v8.gyp:v8', - 'deps/v8/tools/gyp/v8.gyp:v8_libplatform' + 'deps/v8/tools/gyp/v8.gyp:v8_libplatform', + 'deps/uv/uv.gyp:libuv', ], 'include_dirs': [ 'src', diff --git a/src/async-wrap.cc b/src/async-wrap.cc index a7a9822238329a..a2084173f2b02f 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -19,7 +19,6 @@ using v8::Isolate; using v8::Local; using v8::Object; using v8::RetainedObjectInfo; -using v8::TryCatch; using v8::Value; namespace node { @@ -175,6 +174,8 @@ void LoadAsyncWrapperInfo(Environment* env) { Local AsyncWrap::MakeCallback(const Local cb, int argc, Local* argv) { + if (!env()->CanCallIntoJs()) + return Undefined(env()->isolate()); CHECK(env()->context() == env()->isolate()->GetCurrentContext()); Local pre_fn = env()->async_hooks_pre_function(); @@ -200,22 +201,33 @@ Local AsyncWrap::MakeCallback(const Local cb, Local enter_v = domain->Get(env()->enter_string()); if (enter_v->IsFunction()) { if (enter_v.As()->Call(domain, 0, nullptr).IsEmpty()) { - FatalError("node::AsyncWrap::MakeCallback", + if (env()->CanCallIntoJs()) + FatalError("node::AsyncWrap::MakeCallback", "domain enter callback threw, please report this"); + else + return Undefined(env()->isolate()); } } } if (ran_init_callback() && !pre_fn.IsEmpty()) { - if (pre_fn->Call(context, 0, nullptr).IsEmpty()) - FatalError("node::AsyncWrap::MakeCallback", "pre hook threw"); + if (pre_fn->Call(context, 0, nullptr).IsEmpty()) { + if (env()->CanCallIntoJs()) + FatalError("node::AsyncWrap::MakeCallback", "pre hook threw"); + else + return Undefined(env()->isolate()); + } } Local ret = cb->Call(context, argc, argv); if (ran_init_callback() && !post_fn.IsEmpty()) { - if (post_fn->Call(context, 0, nullptr).IsEmpty()) - FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); + if (post_fn->Call(context, 0, nullptr).IsEmpty()) { + if (env()->CanCallIntoJs()) + FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); + else + return Undefined(env()->isolate()); + } } if (ret.IsEmpty()) { @@ -226,8 +238,11 @@ Local AsyncWrap::MakeCallback(const Local cb, Local exit_v = domain->Get(env()->exit_string()); if (exit_v->IsFunction()) { if (exit_v.As()->Call(domain, 0, nullptr).IsEmpty()) { - FatalError("node::AsyncWrap::MakeCallback", + if (env()->CanCallIntoJs()) + FatalError("node::AsyncWrap::MakeCallback", "domain exit callback threw, please report this"); + else + return Undefined(env()->isolate()); } } } diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 62ad44bd992f24..41052240cb1afa 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1284,13 +1284,19 @@ static void CaresTimerClose(Environment* env, } +static void InitAresOnce() { + int r = ares_library_init(ARES_LIB_INIT_ALL); + CHECK_EQ(r, ARES_SUCCESS); +} + + static void Initialize(Local target, Local unused, Local context) { - Environment* env = Environment::GetCurrent(context); + static uv_once_t init_once = UV_ONCE_INIT; + uv_once(&init_once, InitAresOnce); - int r = ares_library_init(ARES_LIB_INIT_ALL); - CHECK_EQ(r, ARES_SUCCESS); + Environment* env = Environment::GetCurrent(context); struct ares_options options; memset(&options, 0, sizeof(options)); @@ -1299,10 +1305,12 @@ static void Initialize(Local target, options.sock_state_cb_data = env; /* We do the call to ares_init_option for caller. */ - r = ares_init_options(env->cares_channel_ptr(), - &options, - ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB); + int r = ares_init_options(env->cares_channel_ptr(), + &options, + ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB); CHECK_EQ(r, ARES_SUCCESS); + // Make env call ares_destroy when disposed. + env->set_using_cares(); /* Initialize the timeout timer. The timer won't be started until the */ /* first socket is opened. */ diff --git a/src/env-inl.h b/src/env-inl.h index e1856d81b0060a..13928bdb15cfef 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -7,6 +7,7 @@ #include "util-inl.h" #include "uv.h" #include "v8.h" +#include "persistent-handle-cleanup.h" #include #include @@ -71,6 +72,14 @@ inline Environment::IsolateData::IsolateData(v8::Isolate* isolate, sizeof(StringValue) - 1).ToLocalChecked()), PER_ISOLATE_STRING_PROPERTIES(V) #undef V + +#define V(PropertyName, StringName) \ + PropertyName ## _(isolate, \ + v8::Symbol::New(isolate, \ + FIXED_ONE_BYTE_STRING(isolate, \ + StringName))), + PER_ISOLATE_SYMBOL_PROPERTIES(V) +#undef V ref_count_(0) {} inline uv_loop_t* Environment::IsolateData::event_loop() const { @@ -178,8 +187,17 @@ inline void Environment::ArrayBufferAllocatorInfo::reset_fill_flag() { } inline Environment* Environment::New(v8::Local context, - uv_loop_t* loop) { - Environment* env = new Environment(context, loop); + uv_loop_t* loop, + WorkerContext* worker_context) { + bool is_worker = worker_context != nullptr; + size_t thread_id = is_worker ? GenerateThreadId() : 0; + Environment* env = new Environment(context, loop, thread_id); + if (is_worker) { + env->set_worker_context(worker_context); + worker_context->set_worker_env(env); + env->set_owner_env(worker_context->owner_env()); + } + env->AssignToContext(context); return env; } @@ -214,7 +232,8 @@ inline Environment* Environment::GetCurrent( } inline Environment::Environment(v8::Local context, - uv_loop_t* loop) + uv_loop_t* loop, + size_t thread_id) : isolate_(context->GetIsolate()), isolate_data_(IsolateData::GetOrCreate(context->GetIsolate(), loop)), timer_base_(uv_now(loop)), @@ -224,6 +243,7 @@ inline Environment::Environment(v8::Local context, makecallback_cntr_(0), async_wrap_uid_(0), debugger_agent_(this), + thread_id_(thread_id), http_parser_buffer_(nullptr), context_(context->GetIsolate(), context) { // We'll be creating new objects so make sure we've entered the context. @@ -244,8 +264,12 @@ inline Environment::Environment(v8::Local context, } inline Environment::~Environment() { + CHECK_EQ(sub_worker_context_count(), 0); v8::HandleScope handle_scope(isolate()); + PersistentHandleCleanup cleanup(this); + isolate()->VisitHandlesWithClassIds(&cleanup); + context()->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex, nullptr); #define V(PropertyName, TypeName) PropertyName ## _.Reset(); @@ -256,6 +280,27 @@ inline Environment::~Environment() { delete[] heap_statistics_buffer_; delete[] heap_space_statistics_buffer_; delete[] http_parser_buffer_; + + if (using_cares_) { + ares_destroy(ares_channel()); + using_cares_ = false; + } + + // For valgrind. The main environment cannot deal with CleanupHandles() + // for some reason. + while (HandleCleanup* hc = handle_cleanup_queue_.PopFront()) + delete hc; +} + +inline void Environment::TerminateSubWorkers() { + for (WorkerContext* context : *sub_worker_contexts()) { + // See libuv issue https://github.com/libuv/libuv/issues/276. + context->owner_notifications()->Ref(); + context->Terminate(false); + } + + while (sub_worker_context_count() > 0) + uv_run(event_loop(), UV_RUN_ONCE); } inline void Environment::CleanupHandles() { @@ -318,10 +363,18 @@ inline uv_check_t* Environment::idle_check_handle() { return &idle_check_handle_; } -inline void Environment::RegisterHandleCleanup(uv_handle_t* handle, - HandleCleanupCb cb, - void *arg) { - handle_cleanup_queue_.PushBack(new HandleCleanup(handle, cb, arg)); +inline void Environment::DeregisterHandleCleanup(HandleCleanup* hc) { + handle_cleanup_queue_.Remove(hc); + delete hc; +} + +inline HandleCleanup* Environment::RegisterHandleCleanup( + uv_handle_t* handle, + HandleCleanupCb cb, + void* arg) { + HandleCleanup* hc = new HandleCleanup(handle, cb, arg); + handle_cleanup_queue_.PushBack(hc); + return hc; } inline void Environment::FinishHandleCleanup(uv_handle_t* handle) { @@ -407,6 +460,45 @@ inline void Environment::set_http_parser_buffer(char* buffer) { http_parser_buffer_ = buffer; } +inline WorkerContext* Environment::worker_context() const { + CHECK(is_worker_thread()); + CHECK_NE(worker_context_, nullptr); + return worker_context_; +} + +inline void Environment::set_worker_context(WorkerContext* context) { + CHECK_EQ(worker_context_, nullptr); + CHECK_NE(context, nullptr); + worker_context_ = context; +} + +inline Environment* Environment::owner_env() const { + CHECK_NE(owner_env_, nullptr); + return owner_env_; +} + +inline void Environment::set_owner_env(Environment* env) { + CHECK_EQ(owner_env_, nullptr); + CHECK_NE(env, nullptr); + owner_env_ = env; +} + +inline size_t Environment::thread_id() const { + return thread_id_; +} + +inline bool Environment::is_main_thread() const { + return worker_context_ == nullptr; +} + +inline bool Environment::is_worker_thread() const { + return !is_main_thread(); +} + +inline size_t Environment::sub_worker_context_count() const { + return sub_worker_context_count_; +}; + inline Environment* Environment::from_cares_timer_handle(uv_timer_t* handle) { return ContainerOf(&Environment::cares_timer_handle_, handle); } @@ -428,6 +520,10 @@ inline ares_task_list* Environment::cares_task_list() { return &cares_task_list_; } +inline void Environment::set_using_cares() { + using_cares_ = true; +} + inline Environment::IsolateData* Environment::isolate_data() const { return isolate_data_; } @@ -531,6 +627,55 @@ inline void Environment::SetTemplateMethod(v8::Local that, function->SetName(name_string); // NODE_SET_METHOD() compatibility. } +inline void Environment::AddSubWorkerContext(WorkerContext* context) { + sub_worker_context_count_++; + sub_worker_context_list_dirty_ = true; + sub_worker_contexts()->PushBack(context); +} + +inline void Environment::RemoveSubWorkerContext(WorkerContext* context) { + sub_worker_context_count_--; + sub_worker_context_list_dirty_ = true; + sub_worker_contexts()->Remove(context); +} + +inline Environment::WorkerContextList* Environment::sub_worker_contexts() { + return &sub_worker_contexts_; +} + +inline uv_mutex_t* Environment::ApiMutex() { + return is_worker_thread() ? worker_context()->api_mutex() : nullptr; +} + +inline bool Environment::CanCallIntoJs() const { + return is_main_thread() || + (is_worker_thread() && worker_context()->JsExecutionAllowed()); +} + +inline void Environment::Exit(int exit_code) { + if (is_main_thread()) + exit(exit_code); + else + worker_context()->Exit(exit_code); +} + +inline void Environment::ProcessNotifications() { + if (is_worker_thread()) + WorkerContext::WorkerNotificationCallback(worker_context()); + + process_owner_notifications: + sub_worker_context_list_dirty_ = false; + for (WorkerContext* context : *sub_worker_contexts()) { + WorkerContext::OwnerNotificationCallback(context); + // The loop needs to be restarted if the list changed while in-loop. + // The list will only change if a worker is being terminated abruptly, + // which is a rare occurrence, so restarting the loop should not be a + // problem performance-wise. + if (sub_worker_context_list_dirty_) + goto process_owner_notifications; + } +} + inline v8::Local Environment::NewInternalFieldObject() { v8::MaybeLocal m_obj = generic_internal_field_template()->NewInstance(context()); @@ -563,6 +708,21 @@ inline v8::Local Environment::NewInternalFieldObject() { #undef VS #undef VP +#define V(PropertyName, StringName) \ + inline \ + v8::Local Environment::IsolateData::PropertyName() const { \ + return const_cast(this)->PropertyName ## _.Get(isolate()); \ + } + PER_ISOLATE_SYMBOL_PROPERTIES(V) +#undef V + +#define V(PropertyName, StringName) \ + inline v8::Local Environment::PropertyName() const { \ + return isolate_data()->PropertyName(); \ + } + PER_ISOLATE_SYMBOL_PROPERTIES(V) +#undef V + #define V(PropertyName, TypeName) \ inline v8::Local Environment::PropertyName() const { \ return StrongPersistentToLocal(PropertyName ## _); \ @@ -576,6 +736,7 @@ inline v8::Local Environment::NewInternalFieldObject() { #undef ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES #undef PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES #undef PER_ISOLATE_STRING_PROPERTIES +#undef PER_ISOLATE_SYMBOL_PROPERTIES } // namespace node diff --git a/src/env.h b/src/env.h index 80f43036f2dd51..c89754d44e45bf 100644 --- a/src/env.h +++ b/src/env.h @@ -9,6 +9,8 @@ #include "util.h" #include "uv.h" #include "v8.h" +#include "worker.h" +#include "async-wrap.h" #include @@ -104,6 +106,7 @@ namespace node { V(exit_code_string, "exitCode") \ V(exit_string, "exit") \ V(expire_string, "expire") \ + V(experimental_workers_string, "experimental_workers") \ V(exponent_string, "exponent") \ V(exports_string, "exports") \ V(ext_key_usage_string, "ext_key_usage") \ @@ -277,6 +280,27 @@ namespace node { V(udp_constructor_function, v8::Function) \ V(write_wrap_constructor_function, v8::Function) \ +#define PER_ISOLATE_SYMBOL_PROPERTIES(V) \ + V(worker_init_symbol, "worker_init") \ + + +// All weak persistent handles that need to be walked upon Environment +// destruction should have defined a class id. Finalizers are not called even +// when a V8 isolate (and its heap) are destroyed so non-JS resources are +// easily leaked. The clean-ups for these are defined in +// persistent-handle-cleanup.cc. +// +// The cleanup for resources that are backing strong persistent handles is +// ensured in some other way, e.g. RegisterHandleCleanup. +enum ClassId : uint16_t { + CONTEXTIFY_SCRIPT = 0xA10C + 1, +#define V(PROVIDER) \ + PROVIDER_CLASS_ID_ ## PROVIDER = \ + AsyncWrap::PROVIDER_ ## PROVIDER + NODE_ASYNC_ID_OFFSET, + NODE_ASYNC_PROVIDER_TYPES(V) +#undef V +}; + class Environment; // TODO(bnoordhuis) Rename struct, the ares_ prefix implies it's part @@ -290,6 +314,25 @@ struct ares_task_t { RB_HEAD(ares_task_list, ares_task_t); +typedef void (*HandleCleanupCb)(Environment* env, + uv_handle_t* handle, + void* arg); +class HandleCleanup { + private: + friend class Environment; + + HandleCleanup(uv_handle_t* handle, HandleCleanupCb cb, void* arg) + : handle_(handle), + cb_(cb), + arg_(arg) { + } + + uv_handle_t* handle_; + HandleCleanupCb cb_; + void* arg_; + ListNode handle_cleanup_queue_; +}; + class Environment { public: class AsyncHooks { @@ -391,26 +434,6 @@ class Environment { DISALLOW_COPY_AND_ASSIGN(ArrayBufferAllocatorInfo); }; - typedef void (*HandleCleanupCb)(Environment* env, - uv_handle_t* handle, - void* arg); - - class HandleCleanup { - private: - friend class Environment; - - HandleCleanup(uv_handle_t* handle, HandleCleanupCb cb, void* arg) - : handle_(handle), - cb_(cb), - arg_(arg) { - } - - uv_handle_t* handle_; - HandleCleanupCb cb_; - void* arg_; - ListNode handle_cleanup_queue_; - }; - static inline Environment* GetCurrent(v8::Isolate* isolate); static inline Environment* GetCurrent(v8::Local context); static inline Environment* GetCurrent( @@ -422,7 +445,8 @@ class Environment { // See CreateEnvironment() in src/node.cc. static inline Environment* New(v8::Local context, - uv_loop_t* loop); + uv_loop_t* loop, + WorkerContext* worker_context = nullptr); inline void CleanupHandles(); inline void Dispose(); @@ -445,10 +469,11 @@ class Environment { inline uv_check_t* idle_check_handle(); // Register clean-up cb to be called on env->Dispose() - inline void RegisterHandleCleanup(uv_handle_t* handle, - HandleCleanupCb cb, - void *arg); + inline HandleCleanup* RegisterHandleCleanup(uv_handle_t* handle, + HandleCleanupCb cb, + void *arg); inline void FinishHandleCleanup(uv_handle_t* handle); + inline void DeregisterHandleCleanup(HandleCleanup* hc); inline AsyncHooks* async_hooks(); inline DomainFlag* domain_flag(); @@ -461,6 +486,7 @@ class Environment { inline ares_channel cares_channel(); inline ares_channel* cares_channel_ptr(); inline ares_task_list* cares_task_list(); + inline void set_using_cares(); inline bool using_domains() const; inline void set_using_domains(bool value); @@ -484,6 +510,19 @@ class Environment { inline char* http_parser_buffer() const; inline void set_http_parser_buffer(char* buffer); + inline WorkerContext* worker_context() const; + inline void set_worker_context(WorkerContext* context); + + inline Environment* owner_env() const; + inline void set_owner_env(Environment* env); + + inline size_t thread_id() const; + + inline bool is_main_thread() const; + inline bool is_worker_thread() const; + + inline size_t sub_worker_context_count() const; + inline void ThrowError(const char* errmsg); inline void ThrowTypeError(const char* errmsg); inline void ThrowRangeError(const char* errmsg); @@ -520,6 +559,19 @@ class Environment { inline v8::Local NewInternalFieldObject(); + typedef + ListHead WorkerContextList; + + inline uv_mutex_t* ApiMutex(); + inline bool CanCallIntoJs() const; + inline void AddSubWorkerContext(WorkerContext* context); + inline void RemoveSubWorkerContext(WorkerContext* context); + inline WorkerContextList* sub_worker_contexts(); + inline void Exit(int exit_code = 0); + inline void ProcessNotifications(); + inline void TerminateSubWorkers(); + // Strings and private symbols are shared across shared contexts // The getters simply proxy to the per-isolate primitive. #define VP(PropertyName, StringValue) V(v8::Private, PropertyName, StringValue) @@ -538,6 +590,11 @@ class Environment { ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) #undef V +#define V(PropertyName, StringName) \ + inline v8::Local PropertyName() const; + PER_ISOLATE_SYMBOL_PROPERTIES(V) +#undef V + inline debugger::Agent* debugger_agent() { return &debugger_agent_; } @@ -555,12 +612,16 @@ class Environment { static const int kIsolateSlot = NODE_ISOLATE_SLOT; class IsolateData; - inline Environment(v8::Local context, uv_loop_t* loop); + inline Environment(v8::Local context, + uv_loop_t* loop, + size_t thread_id); inline ~Environment(); inline IsolateData* isolate_data() const; v8::Isolate* const isolate_; IsolateData* const isolate_data_; + WorkerContext* worker_context_ = nullptr; + Environment* owner_env_ = nullptr; uv_check_t immediate_check_handle_; uv_idle_t immediate_idle_handle_; uv_prepare_t idle_prepare_handle_; @@ -578,13 +639,18 @@ class Environment { bool trace_sync_io_; size_t makecallback_cntr_; int64_t async_wrap_uid_; + bool using_cares_ = false; debugger::Agent debugger_agent_; HandleWrapQueue handle_wrap_queue_; ReqWrapQueue req_wrap_queue_; ListHead handle_cleanup_queue_; - int handle_cleanup_waiting_; + WorkerContextList sub_worker_contexts_; + size_t sub_worker_context_count_ = 0; + size_t handle_cleanup_waiting_ = 0; + size_t const thread_id_; + bool sub_worker_context_list_dirty_ = false; uint32_t* heap_statistics_buffer_ = nullptr; uint32_t* heap_space_statistics_buffer_ = nullptr; @@ -614,6 +680,11 @@ class Environment { #undef VS #undef VP +#define V(PropertyName, StringName) \ + inline v8::Local PropertyName() const; + PER_ISOLATE_SYMBOL_PROPERTIES(V) +#undef V + private: inline static IsolateData* Get(v8::Isolate* isolate); inline explicit IsolateData(v8::Isolate* isolate, uv_loop_t* loop); @@ -632,6 +703,11 @@ class Environment { #undef VS #undef VP +#define V(PropertyName, StringName) \ + v8::Eternal PropertyName ## _; + PER_ISOLATE_SYMBOL_PROPERTIES(V) +#undef V + unsigned int ref_count_; DISALLOW_COPY_AND_ASSIGN(IsolateData); diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc index 7768f94459c16a..ec6aeb614366ca 100644 --- a/src/fs_event_wrap.cc +++ b/src/fs_event_wrap.cc @@ -36,6 +36,7 @@ class FSEventWrap: public HandleWrap { FSEventWrap(Environment* env, Local object); virtual ~FSEventWrap() override; + static void HandleWillForceClose(HandleWrap* wrap, uv_handle_t* handle); static void OnEvent(uv_fs_event_t* handle, const char* filename, int events, int status); @@ -98,6 +99,8 @@ void FSEventWrap::Start(const FunctionCallbackInfo& args) { int err = uv_fs_event_init(wrap->env()->event_loop(), &wrap->handle_); if (err == 0) { + wrap->RegisterHandleCleanup(reinterpret_cast(&wrap->handle_), + HandleWillForceClose); wrap->initialized_ = true; err = uv_fs_event_start(&wrap->handle_, OnEvent, *path, flags); @@ -163,6 +166,11 @@ void FSEventWrap::OnEvent(uv_fs_event_t* handle, const char* filename, } +void FSEventWrap::HandleWillForceClose(HandleWrap* wrap, uv_handle_t* handle) { + static_cast(wrap)->initialized_ = false; +} + + void FSEventWrap::Close(const FunctionCallbackInfo& args) { FSEventWrap* wrap = Unwrap(args.Holder()); diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index 43c5490eefa888..3372daff4b8e45 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -92,14 +92,63 @@ void HandleWrap::OnClose(uv_handle_t* handle) { Context::Scope context_scope(env->context()); Local object = wrap->object(); - if (wrap->flags_ & kCloseCallback) { + if (wrap->flags_ & kCloseCallback) wrap->MakeCallback(env->onclose_string(), 0, nullptr); + + if (wrap->flags_ & kEnvironmentCleanup) { + CHECK_EQ(wrap->hc_, nullptr); + env->FinishHandleCleanup(handle); + } else if (wrap->hc_ != nullptr) { + // Prevent double clean ups. + env->DeregisterHandleCleanup(wrap->hc_); + wrap->hc_ = nullptr; } - object->SetAlignedPointerInInternalField(0, nullptr); + node::ClearWrap(object); wrap->persistent().Reset(); + handle->data = nullptr; delete wrap; } +void HandleWrap::HandleCleanupCallback(Environment* env, + uv_handle_t* handle, + void* arg) { + if (handle->data == nullptr) + return; + + HandleWrap* wrap = static_cast(handle->data); + // At this point Environment has freed the HandleCleanup* and this is the + // first opportunity to nullify it. + // TODO(petkaantonov) it would be more symmetric with DeregisterHandleCleanup + // if the HandleCleanup* was deleted in FinishHandleCleanup but it requires + // changing all other users of RegisterHandleCleanup + wrap->hc_ = nullptr; + if (handle != nullptr && !uv_is_closing(handle)) { + if (arg != nullptr) { + CallbackContainer* container = static_cast(arg); + container->cb_(wrap, handle); + delete container; + } + // Removes CloseCallback flag as well, EnvironmentCleanup means we are + // exiting. + wrap->flags_ = kEnvironmentCleanup; + uv_close(handle, OnClose); + wrap->handle__ = nullptr; + } +} + + +void HandleWrap::RegisterHandleCleanup(uv_handle_t* handle, + HandleWillForceCloseCb cb) { + CHECK_EQ(hc_, nullptr); + + CallbackContainer* container = + cb == nullptr ? nullptr : new CallbackContainer(cb); + + hc_ = env()->RegisterHandleCleanup(handle, + HandleCleanupCallback, + container); +} + } // namespace node diff --git a/src/handle_wrap.h b/src/handle_wrap.h index da712b33befbcc..cc0fe35d3929fc 100644 --- a/src/handle_wrap.h +++ b/src/handle_wrap.h @@ -9,6 +9,7 @@ namespace node { class Environment; +class HandleCleanup; // Rules: // @@ -32,6 +33,8 @@ class Environment; class HandleWrap : public AsyncWrap { public: + typedef void (*HandleWillForceCloseCb)(HandleWrap* wrap, uv_handle_t* handle); + static void Close(const v8::FunctionCallbackInfo& args); static void Ref(const v8::FunctionCallbackInfo& args); static void Unref(const v8::FunctionCallbackInfo& args); @@ -49,19 +52,31 @@ class HandleWrap : public AsyncWrap { AsyncWrap::ProviderType provider, AsyncWrap* parent = nullptr); virtual ~HandleWrap() override; + virtual void RegisterHandleCleanup(uv_handle_t* handle, + HandleWillForceCloseCb cb = nullptr); private: + class CallbackContainer { + explicit CallbackContainer(HandleWillForceCloseCb cb) : cb_(cb) {} + HandleWillForceCloseCb cb_; + friend class HandleWrap; + }; + static void HandleCleanupCallback(Environment* env, + uv_handle_t* handle, + void* arg); friend class Environment; friend void GetActiveHandles(const v8::FunctionCallbackInfo&); static void OnClose(uv_handle_t* handle); ListNode handle_wrap_queue_; unsigned int flags_; + HandleCleanup* hc_ = nullptr; // Using double underscore due to handle_ member in tcp_wrap. Probably // tcp_wrap should rename it's member to 'handle'. uv_handle_t* handle__; static const unsigned int kUnref = 1; static const unsigned int kCloseCallback = 2; + static const unsigned int kEnvironmentCleanup = 4; }; diff --git a/src/node-contextify.h b/src/node-contextify.h new file mode 100644 index 00000000000000..7e7036bb043589 --- /dev/null +++ b/src/node-contextify.h @@ -0,0 +1,59 @@ +#ifndef SRC_NODE_CONTEXTIFY_H_ +#define SRC_NODE_CONTEXTIFY_H_ + +#include "base-object.h" +#include "env.h" +#include "v8.h" + +namespace node { + +class ContextifyScript : public BaseObject { + public: + ContextifyScript(Environment* env, v8::Local object); + ~ContextifyScript() override; + + void Dispose(); + static void Init(Environment* env, v8::Local target); + // args: code, [options] + static void New(const v8::FunctionCallbackInfo& args); + static bool InstanceOf(Environment* env, const v8::Local& value); + // args: [options] + static void RunInThisContext( + const v8::FunctionCallbackInfo& args); + // args: sandbox, [options] + static void RunInContext(const v8::FunctionCallbackInfo& args); + static int64_t GetTimeoutArg( + const v8::FunctionCallbackInfo& args, const int i); + static bool GetDisplayErrorsArg( + const v8::FunctionCallbackInfo& args, const int i); + static void DecorateErrorStack(Environment* env, + const v8::TryCatch& try_catch); + static v8::Local GetFilenameArg( + const v8::FunctionCallbackInfo& args, const int i); + static v8::MaybeLocal GetCachedData( + Environment* env, + const v8::FunctionCallbackInfo& args, + const int i); + static v8::Local GetLineOffsetArg( + const v8::FunctionCallbackInfo& args, + const int i); + static v8::Local GetColumnOffsetArg( + const v8::FunctionCallbackInfo& args, + const int i); + static bool GetProduceCachedData( + Environment* env, + const v8::FunctionCallbackInfo& args, + const int i); + + static bool EvalMachine(Environment* env, + const int64_t timeout, + const bool display_errors, + const v8::FunctionCallbackInfo& args, + v8::TryCatch& try_catch); + private: + v8::Persistent script_; +}; + +} // namespace node + +#endif // SRC_NODE_CONTEXTIFY_H_ diff --git a/src/node.cc b/src/node.cc index 05984745de080f..bf5386ca444b38 100644 --- a/src/node.cc +++ b/src/node.cc @@ -43,6 +43,7 @@ #include "v8-debug.h" #include "v8-profiler.h" #include "zlib.h" +#include "worker.h" #ifdef NODE_ENABLE_VTUNE_PROFILING #include "../deps/v8/src/third_party/vtune/v8-vtune.h" @@ -138,7 +139,6 @@ static bool syntax_check_only = false; static bool trace_deprecation = false; static bool throw_deprecation = false; static bool trace_sync_io = false; -static bool track_heap_objects = false; static const char* eval_string = nullptr; static unsigned int preload_module_count = 0; static const char** preload_modules = nullptr; @@ -153,22 +153,32 @@ static node_module* modlist_builtin; static node_module* modlist_linked; static node_module* modlist_addon; +// used by C++ modules as well +bool track_heap_objects = false; +bool no_deprecation = false; +bool experimental_workers = false; + #if defined(NODE_HAVE_I18N_SUPPORT) // Path to ICU data (for i18n / Intl) static const char* icu_data_dir = nullptr; #endif -// used by C++ modules as well -bool no_deprecation = false; +v8::Platform* default_platform = nullptr; // process-relative uptime base, initialized at start-up static double prog_start_time; static bool debugger_running; +// Needed for potentially non-thread-safe process-globals +static uv_mutex_t process_mutex; +// Workers have read-only access to process-globals but cannot write them +static uv_rwlock_t process_rwlock; static uv_async_t dispatch_debug_messages_async; static node::atomic node_isolate; -static v8::Platform* default_platform; +static uv_thread_t process_main_thread; +static size_t thread_id_counter = 1; +static uv_mutex_t thread_id_counter_mutex; static void PrintErrorString(const char* format, ...) { va_list ap; @@ -1131,6 +1141,8 @@ Local MakeCallback(Environment* env, const Local callback, int argc, Local argv[]) { + if (!env->CanCallIntoJs()) + return Undefined(env->isolate()); // If you hit this assertion, you forgot to enter the v8::Context first. CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); @@ -1166,22 +1178,33 @@ Local MakeCallback(Environment* env, Local enter_v = domain->Get(env->enter_string()); if (enter_v->IsFunction()) { if (enter_v.As()->Call(domain, 0, nullptr).IsEmpty()) { - FatalError("node::MakeCallback", - "domain enter callback threw, please report this"); + if (env->CanCallIntoJs()) + FatalError("node::MakeCallback", + "domain enter callback threw, please report this"); + else + return Undefined(env->isolate()); } } } if (ran_init_callback && !pre_fn.IsEmpty()) { - if (pre_fn->Call(object, 0, nullptr).IsEmpty()) - FatalError("node::MakeCallback", "pre hook threw"); + if (pre_fn->Call(object, 0, nullptr).IsEmpty()) { + if (env->CanCallIntoJs()) + FatalError("node::MakeCallback", "pre hook threw"); + else + return Undefined(env->isolate()); + } } Local ret = callback->Call(recv, argc, argv); if (ran_init_callback && !post_fn.IsEmpty()) { - if (post_fn->Call(object, 0, nullptr).IsEmpty()) - FatalError("node::MakeCallback", "post hook threw"); + if (post_fn->Call(object, 0, nullptr).IsEmpty()) { + if (env->CanCallIntoJs()) + FatalError("node::MakeCallback", "post hook threw"); + else + return Undefined(env->isolate()); + } } if (ret.IsEmpty()) { @@ -1192,8 +1215,11 @@ Local MakeCallback(Environment* env, Local exit_v = domain->Get(env->exit_string()); if (exit_v->IsFunction()) { if (exit_v.As()->Call(domain, 0, nullptr).IsEmpty()) { - FatalError("node::MakeCallback", + if (env->CanCallIntoJs()) + FatalError("node::MakeCallback", "domain exit callback threw, please report this"); + else + return Undefined(env->isolate()); } } } @@ -1508,6 +1534,7 @@ void AppendExceptionLine(Environment* env, if (env->printed_error()) return; env->set_printed_error(true); + ScopedLock::Mutex lock(&process_mutex); uv_tty_reset_mode(); PrintErrorString("\n%s", arrow); } @@ -1607,13 +1634,15 @@ static Local ExecuteString(Environment* env, Local script = v8::Script::Compile(source, filename); if (script.IsEmpty()) { ReportException(env, try_catch); - exit(3); + env->Exit(3); + return Local(); } Local result = script->Run(); if (result.IsEmpty()) { ReportException(env, try_catch); - exit(4); + env->Exit(4); + return Local(); } return scope.Escape(result); @@ -1688,6 +1717,7 @@ static void Abort(const FunctionCallbackInfo& args) { static void Chdir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); if (args.Length() != 1 || !args[0]->IsString()) { return env->ThrowTypeError("Bad argument."); @@ -1731,6 +1761,8 @@ static void Umask(const FunctionCallbackInfo& args) { if (args.Length() < 1 || args[0]->IsUndefined()) { old = umask(0); umask(static_cast(old)); + } else if (env->is_worker_thread()) { + return env->ThrowRangeError("cannot set umask from a worker"); } else if (!args[0]->IsInt32() && !args[0]->IsString()) { return env->ThrowTypeError("argument must be an integer or octal string."); } else { @@ -1887,6 +1919,7 @@ static void GetEGid(const FunctionCallbackInfo& args) { static void SetGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); if (!args[0]->IsUint32() && !args[0]->IsString()) { return env->ThrowTypeError("setgid argument must be a number or a string"); @@ -1906,6 +1939,7 @@ static void SetGid(const FunctionCallbackInfo& args) { static void SetEGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); if (!args[0]->IsUint32() && !args[0]->IsString()) { return env->ThrowTypeError("setegid argument must be a number or string"); @@ -1925,6 +1959,7 @@ static void SetEGid(const FunctionCallbackInfo& args) { static void SetUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); if (!args[0]->IsUint32() && !args[0]->IsString()) { return env->ThrowTypeError("setuid argument must be a number or a string"); @@ -1944,6 +1979,7 @@ static void SetUid(const FunctionCallbackInfo& args) { static void SetEUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); if (!args[0]->IsUint32() && !args[0]->IsString()) { return env->ThrowTypeError("seteuid argument must be a number or string"); @@ -2081,7 +2117,7 @@ static void InitGroups(const FunctionCallbackInfo& args) { void Exit(const FunctionCallbackInfo& args) { - exit(args[0]->Int32Value()); + Environment::GetCurrent(args)->Exit(args[0]->Int32Value()); } @@ -2090,6 +2126,8 @@ static void Uptime(const FunctionCallbackInfo& args) { double uptime; uv_update_time(env->event_loop()); + // TODO(petkaantonov) Will report the main instance's uptime even when + // called inside a worker instance uptime = uv_now(env->event_loop()) - prog_start_time; args.GetReturnValue().Set(Number::New(env->isolate(), uptime / 1000)); @@ -2318,7 +2356,7 @@ void FatalException(Isolate* isolate, // failed before the process._fatalException function was added! // this is probably pretty bad. Nothing to do but report and exit. ReportException(env, error, message); - exit(6); + return env->Exit(6); } TryCatch fatal_try_catch; @@ -2333,12 +2371,12 @@ void FatalException(Isolate* isolate, if (fatal_try_catch.HasCaught()) { // the fatal exception function threw, so we must exit ReportException(env, fatal_try_catch); - exit(7); + return env->Exit(7); } if (false == caught->BooleanValue()) { ReportException(env, error, message); - exit(1); + return env->Exit(1); } } @@ -2360,6 +2398,9 @@ void OnMessage(Local message, Local error) { static void Binding(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); + ScopedLock::Mutex lock(env->ApiMutex()); + if (!env->CanCallIntoJs()) + return; Local module = args[0]->ToString(env->isolate()); node::Utf8Value module_v(env->isolate(), module); @@ -2458,7 +2499,12 @@ static void LinkedBinding(const FunctionCallbackInfo& args) { static void ProcessTitleGetter(Local property, const PropertyCallbackInfo& info) { char buffer[512]; - uv_get_process_title(buffer, sizeof(buffer)); + { + // FIXME(petkaantonov) remove this when + // https://github.com/libuv/libuv/issues/271 is resolved. + ScopedLock::Read lock(&process_rwlock); + uv_get_process_title(buffer, sizeof(buffer)); + } info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buffer)); } @@ -2467,7 +2513,9 @@ static void ProcessTitleSetter(Local property, Local value, const PropertyCallbackInfo& info) { node::Utf8Value title(info.GetIsolate(), value); - // TODO(piscisaureus): protect with a lock + // FIXME(petkaantonov) remove this when + // https://github.com/libuv/libuv/issues/271 is resolved. + ScopedLock::Write lock(&process_rwlock); uv_set_process_title(*title); } @@ -2475,6 +2523,7 @@ static void ProcessTitleSetter(Local property, static void EnvGetter(Local property, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); + ScopedLock::Read lock(&process_rwlock); #ifdef __POSIX__ node::Utf8Value key(isolate, property); const char* val = getenv(*key); @@ -2503,6 +2552,7 @@ static void EnvGetter(Local property, static void EnvSetter(Local property, Local value, const PropertyCallbackInfo& info) { + ScopedLock::Write lock(&process_rwlock); #ifdef __POSIX__ node::Utf8Value key(info.GetIsolate(), property); node::Utf8Value val(info.GetIsolate(), value); @@ -2524,6 +2574,7 @@ static void EnvSetter(Local property, static void EnvQuery(Local property, const PropertyCallbackInfo& info) { int32_t rc = -1; // Not found unless proven otherwise. + ScopedLock::Read lock(&process_rwlock); #ifdef __POSIX__ node::Utf8Value key(info.GetIsolate(), property); if (getenv(*key)) @@ -2550,6 +2601,7 @@ static void EnvQuery(Local property, static void EnvDeleter(Local property, const PropertyCallbackInfo& info) { bool rc = true; + ScopedLock::Write lock(&process_rwlock); #ifdef __POSIX__ node::Utf8Value key(info.GetIsolate(), property); rc = getenv(*key) != nullptr; @@ -2576,7 +2628,7 @@ static void EnvEnumerator(const PropertyCallbackInfo& info) { Local fn = env->push_values_to_array_function(); Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; size_t idx = 0; - + ScopedLock::Read lock(&process_rwlock); #ifdef __POSIX__ int size = 0; while (environ[size]) @@ -2688,12 +2740,16 @@ static Local GetFeatures(Environment* env) { Boolean::New(env->isolate(), get_builtin_module("crypto") != nullptr)); + obj->Set(env->experimental_workers_string(), + Boolean::New(env->isolate(), experimental_workers)); + return scope.Escape(obj); } static void DebugPortGetter(Local property, const PropertyCallbackInfo& info) { + ScopedLock::Read lock(&process_rwlock); info.GetReturnValue().Set(debug_port); } @@ -2701,6 +2757,7 @@ static void DebugPortGetter(Local property, static void DebugPortSetter(Local property, Local value, const PropertyCallbackInfo& info) { + ScopedLock::Write lock(&process_rwlock); debug_port = value->Int32Value(); } @@ -2807,9 +2864,19 @@ void SetupProcessObject(Environment* env, process->SetAccessor(env->title_string(), ProcessTitleGetter, - ProcessTitleSetter, + env->is_main_thread() ? ProcessTitleSetter : nullptr, env->as_external()); + // process.isMainThread + READONLY_PROPERTY(process, + "isMainThread", + Boolean::New(env->isolate(), env->is_main_thread())); + + // process.isWorkerThread + READONLY_PROPERTY(process, + "isWorkerThread", + Boolean::New(env->isolate(), env->is_worker_thread())); + // process.version READONLY_PROPERTY(process, "version", @@ -2960,15 +3027,18 @@ void SetupProcessObject(Environment* env, // create process.env Local process_env_template = ObjectTemplate::New(env->isolate()); - process_env_template->SetNamedPropertyHandler(EnvGetter, - EnvSetter, - EnvQuery, - EnvDeleter, - EnvEnumerator, - env->as_external()); + process_env_template->SetNamedPropertyHandler( + EnvGetter, + env->is_main_thread() ? EnvSetter : nullptr, + EnvQuery, + env->is_main_thread() ? EnvDeleter : nullptr, + EnvEnumerator, + env->as_external()); Local process_env = process_env_template->NewInstance(); process->Set(env->env_string(), process_env); + READONLY_PROPERTY(process, "tid", Number::New(env->isolate(), + env->thread_id())); READONLY_PROPERTY(process, "pid", Integer::New(env->isolate(), getpid())); READONLY_PROPERTY(process, "features", GetFeatures(env)); process->SetAccessor(env->need_imm_cb_string(), @@ -2976,43 +3046,55 @@ void SetupProcessObject(Environment* env, NeedImmediateCallbackSetter, env->as_external()); - // -e, --eval - if (eval_string) { - READONLY_PROPERTY(process, - "_eval", - String::NewFromUtf8(env->isolate(), eval_string)); - } + if (env->is_main_thread()) { + // -e, --eval + if (eval_string) { + READONLY_PROPERTY(process, + "_eval", + String::NewFromUtf8(env->isolate(), eval_string)); + } - // -p, --print - if (print_eval) { - READONLY_PROPERTY(process, "_print_eval", True(env->isolate())); - } + // -c, --check + if (syntax_check_only) { + READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate())); + } - // -c, --check - if (syntax_check_only) { - READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate())); - } + // -p, --print + if (print_eval) { + READONLY_PROPERTY(process, "_print_eval", True(env->isolate())); + } - // -i, --interactive - if (force_repl) { - READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); - } + // -i, --interactive + if (force_repl) { + READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); + } + + if (preload_module_count) { + CHECK(preload_modules); + Local array = Array::New(env->isolate()); + for (unsigned int i = 0; i < preload_module_count; ++i) { + Local module = String::NewFromUtf8(env->isolate(), + preload_modules[i]); + array->Set(i, module); + } + READONLY_PROPERTY(process, + "_preload_modules", + array); + delete[] preload_modules; + preload_modules = nullptr; + preload_module_count = 0; + } - if (preload_module_count) { - CHECK(preload_modules); - Local array = Array::New(env->isolate()); - for (unsigned int i = 0; i < preload_module_count; ++i) { - Local module = String::NewFromUtf8(env->isolate(), - preload_modules[i]); - array->Set(i, module); + + // --prof-process + if (prof_process) { + READONLY_PROPERTY(process, "profProcess", True(env->isolate())); } - READONLY_PROPERTY(process, - "_preload_modules", - array); + } - delete[] preload_modules; - preload_modules = nullptr; - preload_module_count = 0; + // --trace-deprecation + if (trace_deprecation) { + READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); } // --no-deprecation @@ -3025,16 +3107,6 @@ void SetupProcessObject(Environment* env, READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); } - // --prof-process - if (prof_process) { - READONLY_PROPERTY(process, "profProcess", True(env->isolate())); - } - - // --trace-deprecation - if (trace_deprecation) { - READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); - } - // --security-revert flags #define V(code, _, __) \ do { \ @@ -3061,50 +3133,56 @@ void SetupProcessObject(Environment* env, process->SetAccessor(env->debug_port_string(), DebugPortGetter, - DebugPortSetter, + env->is_main_thread() ? DebugPortSetter : nullptr, env->as_external()); // define various internal methods - env->SetMethod(process, - "_startProfilerIdleNotifier", - StartProfilerIdleNotifier); - env->SetMethod(process, - "_stopProfilerIdleNotifier", - StopProfilerIdleNotifier); env->SetMethod(process, "_getActiveRequests", GetActiveRequests); env->SetMethod(process, "_getActiveHandles", GetActiveHandles); env->SetMethod(process, "reallyExit", Exit); - env->SetMethod(process, "abort", Abort); - env->SetMethod(process, "chdir", Chdir); env->SetMethod(process, "cwd", Cwd); + if (env->is_main_thread()) { + env->SetMethod(process, "abort", Abort); + env->SetMethod(process, + "_startProfilerIdleNotifier", + StartProfilerIdleNotifier); + env->SetMethod(process, + "_stopProfilerIdleNotifier", + StopProfilerIdleNotifier); + env->SetMethod(process, "chdir", Chdir); + } env->SetMethod(process, "umask", Umask); #if defined(__POSIX__) && !defined(__ANDROID__) env->SetMethod(process, "getuid", GetUid); env->SetMethod(process, "geteuid", GetEUid); - env->SetMethod(process, "setuid", SetUid); - env->SetMethod(process, "seteuid", SetEUid); - - env->SetMethod(process, "setgid", SetGid); - env->SetMethod(process, "setegid", SetEGid); env->SetMethod(process, "getgid", GetGid); env->SetMethod(process, "getegid", GetEGid); - env->SetMethod(process, "getgroups", GetGroups); - env->SetMethod(process, "setgroups", SetGroups); - env->SetMethod(process, "initgroups", InitGroups); + + if (env->is_main_thread()) { + env->SetMethod(process, "setuid", SetUid); + env->SetMethod(process, "seteuid", SetEUid); + env->SetMethod(process, "setgid", SetGid); + env->SetMethod(process, "setegid", SetEGid); + env->SetMethod(process, "setgroups", SetGroups); + env->SetMethod(process, "initgroups", InitGroups); + } #endif // __POSIX__ && !defined(__ANDROID__) env->SetMethod(process, "_kill", Kill); - env->SetMethod(process, "_debugProcess", DebugProcess); - env->SetMethod(process, "_debugPause", DebugPause); - env->SetMethod(process, "_debugEnd", DebugEnd); + if (env->is_main_thread()) { + env->SetMethod(process, "_debugProcess", DebugProcess); + env->SetMethod(process, "_debugPause", DebugPause); + env->SetMethod(process, "_debugEnd", DebugEnd); + } env->SetMethod(process, "hrtime", Hrtime); - env->SetMethod(process, "dlopen", DLOpen); + if (env->is_main_thread()) + env->SetMethod(process, "dlopen", DLOpen); env->SetMethod(process, "uptime", Uptime); env->SetMethod(process, "memoryUsage", MemoryUsage); @@ -3123,6 +3201,7 @@ void SetupProcessObject(Environment* env, #undef READONLY_PROPERTY +#undef READONLY_DONT_ENUM_PROPERTY static void AtExit() { @@ -3167,8 +3246,6 @@ void LoadEnvironment(Environment* env) { // source code.) // The node.js file returns a function 'f' - atexit(AtExit); - TryCatch try_catch; // Disable verbose mode to stop FatalException() handler from trying @@ -3180,7 +3257,7 @@ void LoadEnvironment(Environment* env) { Local f_value = ExecuteString(env, MainSource(env), script_name); if (try_catch.HasCaught()) { ReportException(env, try_catch); - exit(10); + return env->Exit(10); } CHECK(f_value->IsFunction()); Local f = Local::Cast(f_value); @@ -3432,6 +3509,9 @@ static void ParseArgs(int* argc, } else if (strcmp(arg, "--expose-internals") == 0 || strcmp(arg, "--expose_internals") == 0) { // consumed in js + } else if (strcmp(arg, "--experimental-workers") == 0 || + strcmp(arg, "--experimental_workers") == 0) { + experimental_workers = true; } else { // V8 option. Pass through as-is. new_v8_argv[new_v8_argc] = arg; @@ -3767,6 +3847,7 @@ static void DebugEnd(const FunctionCallbackInfo& args) { inline void PlatformInit() { +process_main_thread = uv_thread_self(); #ifdef __POSIX__ sigset_t sigmask; sigemptyset(&sigmask); @@ -3833,6 +3914,7 @@ void Init(int* argc, const char** argv, int* exec_argc, const char*** exec_argv) { + atexit(AtExit); // Initialize prog_start_time to get relative uptime. prog_start_time = static_cast(uv_now(uv_default_loop())); @@ -3939,6 +4021,9 @@ static AtExitCallback* at_exit_functions_; // TODO(bnoordhuis) Turn into per-context event. void RunAtExit(Environment* env) { + if (env->is_worker_thread()) + return; + AtExitCallback* p = at_exit_functions_; at_exit_functions_ = nullptr; @@ -4018,18 +4103,6 @@ Environment* CreateEnvironment(Isolate* isolate, return env; } -static Environment* CreateEnvironment(Isolate* isolate, - Local context, - NodeInstanceData* instance_data) { - return CreateEnvironment(isolate, - instance_data->event_loop(), - context, - instance_data->argc(), - instance_data->argv(), - instance_data->exec_argc(), - instance_data->exec_argv()); -} - static void HandleCloseCb(uv_handle_t* handle) { Environment* env = reinterpret_cast(handle->data); @@ -4037,7 +4110,7 @@ static void HandleCloseCb(uv_handle_t* handle) { } -static void HandleCleanup(Environment* env, +static void HandleCleanupCallback(Environment* env, uv_handle_t* handle, void* arg) { handle->data = env; @@ -4045,6 +4118,27 @@ static void HandleCleanup(Environment* env, } +Environment* CreateEnvironment(Isolate* isolate, + Local context, + WorkerContext* worker_context) { + CHECK_NE(worker_context, nullptr); + HandleScope handle_scope(isolate); + Context::Scope context_scope(context); + Environment* env = Environment::New(context, + worker_context->worker_event_loop(), + worker_context); + InitializeEnvironment(env, + isolate, + worker_context->worker_event_loop(), + context, + worker_context->argc(), + worker_context->argv(), + worker_context->exec_argc(), + worker_context->exec_argv()); + return env; +} + + Environment* CreateEnvironment(Isolate* isolate, uv_loop_t* loop, Local context, @@ -4056,7 +4150,26 @@ Environment* CreateEnvironment(Isolate* isolate, Context::Scope context_scope(context); Environment* env = Environment::New(context, loop); + InitializeEnvironment(env, + isolate, + loop, + context, + argc, + argv, + exec_argc, + exec_argv); + return env; +} + +void InitializeEnvironment(Environment* env, + Isolate* isolate, + uv_loop_t* loop, + Local context, + int argc, + const char* const* argv, + int exec_argc, + const char* const* exec_argv) { isolate->SetAutorunMicrotasks(false); uv_check_init(env->event_loop(), env->immediate_check_handle()); @@ -4082,24 +4195,23 @@ Environment* CreateEnvironment(Isolate* isolate, // Register handle cleanups env->RegisterHandleCleanup( reinterpret_cast(env->immediate_check_handle()), - HandleCleanup, + HandleCleanupCallback, nullptr); env->RegisterHandleCleanup( reinterpret_cast(env->immediate_idle_handle()), - HandleCleanup, + HandleCleanupCallback, nullptr); env->RegisterHandleCleanup( reinterpret_cast(env->idle_prepare_handle()), - HandleCleanup, + HandleCleanupCallback, nullptr); env->RegisterHandleCleanup( reinterpret_cast(env->idle_check_handle()), - HandleCleanup, + HandleCleanupCallback, nullptr); - if (v8_is_profiling) { + if (env->is_main_thread() && v8_is_profiling) StartProfilerIdleNotifier(env); - } Local process_template = FunctionTemplate::New(isolate); process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "process")); @@ -4109,15 +4221,34 @@ Environment* CreateEnvironment(Isolate* isolate, SetupProcessObject(env, argc, argv, exec_argc, exec_argv); LoadAsyncWrapperInfo(env); +} - return env; +// MSVC work-around for intrusive lists. +namespace workaround { +static ListHead cleanup_queue_; +} +static uv_mutex_t cleanup_queue_mutex_; +// Deleting WorkerContexts in response to their notification signals +// will cause use-after-free inside libuv. So the final `delete this` +// call must be made somewhere else. +static void CleanupWorkerContexts() { + ScopedLock::Mutex lock(&cleanup_queue_mutex_); + while (WorkerContext* context = workaround::cleanup_queue_.PopFront()) + delete context; } -// Entry point for new node instances, also called directly for the main -// node instance. -static void StartNodeInstance(void* arg) { - NodeInstanceData* instance_data = static_cast(arg); +void QueueWorkerContextCleanup(WorkerContext* context) { + ScopedLock::Mutex lock(&cleanup_queue_mutex_); + workaround::cleanup_queue_.PushBack(context); +} + + +static int RunMainThread(int argc, + const char** argv, + int exec_argc, + const char** exec_argv) { Isolate::CreateParams params; ArrayBufferAllocator* array_buffer_allocator = new ArrayBufferAllocator(); params.array_buffer_allocator = array_buffer_allocator; @@ -4128,26 +4259,26 @@ static void StartNodeInstance(void* arg) { if (track_heap_objects) { isolate->GetHeapProfiler()->StartTrackingHeapObjects(true); } + CHECK_EQ(nullptr, node_isolate.exchange(isolate)); - // Fetch a reference to the main isolate, so we have a reference to it - // even when we need it to access it from another (debugger) thread. - if (instance_data->is_main()) - CHECK_EQ(nullptr, node_isolate.exchange(isolate)); - + int exit_code = 1; { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); Local context = Context::New(isolate); - Environment* env = CreateEnvironment(isolate, context, instance_data); - array_buffer_allocator->set_env(env); + Environment* env = CreateEnvironment(isolate, + uv_default_loop(), + context, + argc, + argv, + exec_argc, + exec_argv); Context::Scope context_scope(context); - isolate->SetAbortOnUncaughtExceptionCallback( ShouldAbortOnUncaughtException); - // Start debug agent when argv has --debug - if (instance_data->use_debug_agent()) + if (use_debug_agent) StartDebug(env, debug_wait_connect); { @@ -4158,7 +4289,7 @@ static void StartNodeInstance(void* arg) { env->set_trace_sync_io(trace_sync_io); // Enable debugger - if (instance_data->use_debug_agent()) + if (use_debug_agent) EnableDebug(env); { @@ -4167,6 +4298,7 @@ static void StartNodeInstance(void* arg) { do { v8::platform::PumpMessageLoop(default_platform, isolate); more = uv_run(env->event_loop(), UV_RUN_ONCE); + CleanupWorkerContexts(); if (more == false) { v8::platform::PumpMessageLoop(default_platform, isolate); @@ -4182,10 +4314,9 @@ static void StartNodeInstance(void* arg) { } env->set_trace_sync_io(false); + env->TerminateSubWorkers(); - int exit_code = EmitExit(env); - if (instance_data->is_main()) - instance_data->set_exit_code(exit_code); + exit_code = EmitExit(env); RunAtExit(env); #if defined(LEAK_SANITIZER) @@ -4197,18 +4328,35 @@ static void StartNodeInstance(void* arg) { env = nullptr; } - if (instance_data->is_main()) { - // Synchronize with signal handler, see TryStartDebugger. - while (isolate != node_isolate.exchange(nullptr)); // NOLINT - } + // Synchronize with signal handler, see TryStartDebugger. + while (isolate != node_isolate.exchange(nullptr)); // NOLINT CHECK_NE(isolate, nullptr); isolate->Dispose(); isolate = nullptr; delete array_buffer_allocator; + return exit_code; } + +uv_thread_t* main_thread() { + return &process_main_thread; +} + + +size_t GenerateThreadId() { + ScopedLock::Mutex lock(&thread_id_counter_mutex); + size_t ret = thread_id_counter; + thread_id_counter++; + return ret; +} + + int Start(int argc, char** argv) { + CHECK_EQ(uv_mutex_init(&process_mutex), 0); + CHECK_EQ(uv_mutex_init(&thread_id_counter_mutex), 0); + CHECK_EQ(uv_mutex_init(&cleanup_queue_mutex_), 0); + CHECK_EQ(uv_rwlock_init(&process_rwlock), 0); PlatformInit(); CHECK_GT(argc, 0); @@ -4233,25 +4381,22 @@ int Start(int argc, char** argv) { V8::InitializePlatform(default_platform); V8::Initialize(); - int exit_code = 1; - { - NodeInstanceData instance_data(NodeInstanceType::MAIN, - uv_default_loop(), - argc, - const_cast(argv), - exec_argc, - exec_argv, - use_debug_agent); - StartNodeInstance(&instance_data); - exit_code = instance_data.exit_code(); - } + int exit_code = RunMainThread(argc, + const_cast(argv), + exec_argc, + exec_argv); V8::Dispose(); + V8::ShutdownPlatform(); delete default_platform; default_platform = nullptr; delete[] exec_argv; exec_argv = nullptr; + uv_mutex_destroy(&process_mutex); + uv_mutex_destroy(&thread_id_counter_mutex); + uv_mutex_destroy(&cleanup_queue_mutex_); + uv_rwlock_destroy(&process_rwlock); return exit_code; } diff --git a/src/node.js b/src/node.js index ec0e6bf630b5e1..707a7e3f48a6ff 100644 --- a/src/node.js +++ b/src/node.js @@ -52,7 +52,7 @@ // Do not initialize channel in debugger agent, it deletes env variable // and the main thread won't see it. - if (process.argv[1] !== '--debug-agent') + if (process.argv[1] !== '--debug-agent' && process.isMainThread) startup.processChannel(); startup.processRawDebug(); @@ -63,8 +63,24 @@ // are running from a script and running the REPL - but there are a few // others like the debugger or running --eval arguments. Here we decide // which mode we run in. + if (process.isWorkerThread) { + // Sets up the Worker wrapper object + NativeModule.require('worker'); + const path = NativeModule.require('path'); + const Module = NativeModule.require('module'); - if (NativeModule.exists('_third_party_main')) { + if (!process._eval) + process.argv[1] = path.resolve(process.argv[1]); + + process._runMain = function() { + delete process._runMain; + startup.preloadModules(); + if (process._eval) + evalScript('[eval]'); + else + Module.runMain(); + }; + } else if (NativeModule.exists('_third_party_main')) { // To allow people to extend Node in different ways, this hook allows // one to drop a file lib/_third_party_main.js into the build // directory which will be executed instead of Node's normal loading. @@ -288,6 +304,10 @@ // If someone handled it, then great. otherwise, die in C++ land // since that means that we'll exit the process, emit the 'exit' event if (!caught) { + if (process.isWorkerThread) { + NativeModule.require('worker')._workerFatalError(er); + return true; + } try { if (!process._exiting) { process._exiting = true; @@ -653,6 +673,7 @@ }); process.__defineGetter__('stdin', function() { + if (process.isWorkerThread) return null; if (stdin) return stdin; var tty_wrap = process.binding('tty_wrap'); @@ -728,10 +749,12 @@ return stdin; }); - process.openStdin = function() { - process.stdin.resume(); - return process.stdin; - }; + if (process.isMainThread) { + process.openStdin = function() { + process.stdin.resume(); + return process.stdin; + }; + } }; startup.processKillAndExit = function() { @@ -805,7 +828,6 @@ var errnoException = NativeModule.require('util')._errnoException; throw errnoException(err, 'uv_signal_start'); } - signalWraps[type] = wrap; } }); @@ -818,7 +840,6 @@ }); }; - startup.processChannel = function() { // If we were spawned with env NODE_CHANNEL_FD then load that up and // start parsing data from that stream. diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 900b6b922df2be..6ed83711a4b5cb 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -1,3 +1,5 @@ +#include "node-contextify.h" + #include "node.h" #include "node_internals.h" #include "node_watchdog.h" @@ -461,407 +463,411 @@ class ContextifyContext { } }; -class ContextifyScript : public BaseObject { - private: - Persistent script_; - public: - static void Init(Environment* env, Local target) { - HandleScope scope(env->isolate()); - Local class_name = - FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); +void ContextifyScript::Init(Environment* env, Local target) { + HandleScope scope(env->isolate()); + Local class_name = + FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); - Local script_tmpl = env->NewFunctionTemplate(New); - script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); - script_tmpl->SetClassName(class_name); - env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); - env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); + Local script_tmpl = env->NewFunctionTemplate(New); + script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); + script_tmpl->SetClassName(class_name); + env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); + env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); - target->Set(class_name, script_tmpl->GetFunction()); - env->set_script_context_constructor_template(script_tmpl); - } + target->Set(class_name, script_tmpl->GetFunction()); + env->set_script_context_constructor_template(script_tmpl); +} // args: code, [options] - static void New(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); +void ContextifyScript::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (!args.IsConstructCall()) { + return env->ThrowError("Must call vm.Script as a constructor."); + } + + ContextifyScript* contextify_script = + new ContextifyScript(env, args.This()); + contextify_script->persistent().SetWrapperClassId( + ClassId::CONTEXTIFY_SCRIPT); + + TryCatch try_catch; + Local code = args[0]->ToString(env->isolate()); + Local filename = GetFilenameArg(args, 1); + Local lineOffset = GetLineOffsetArg(args, 1); + Local columnOffset = GetColumnOffsetArg(args, 1); + bool display_errors = GetDisplayErrorsArg(args, 1); + MaybeLocal cached_data_buf = GetCachedData(env, args, 1); + bool produce_cached_data = GetProduceCachedData(env, args, 1); + if (try_catch.HasCaught()) { + try_catch.ReThrow(); + return; + } + + ScriptCompiler::CachedData* cached_data = nullptr; + if (!cached_data_buf.IsEmpty()) { + Local ui8 = cached_data_buf.ToLocalChecked(); + ArrayBuffer::Contents contents = ui8->Buffer()->GetContents(); + cached_data = new ScriptCompiler::CachedData( + static_cast(contents.Data()) + ui8->ByteOffset(), + ui8->ByteLength()); + } + + ScriptOrigin origin(filename, lineOffset, columnOffset); + ScriptCompiler::Source source(code, origin, cached_data); + ScriptCompiler::CompileOptions compile_options = + ScriptCompiler::kNoCompileOptions; + + if (source.GetCachedData() != nullptr) + compile_options = ScriptCompiler::kConsumeCodeCache; + else if (produce_cached_data) + compile_options = ScriptCompiler::kProduceCodeCache; + + Local v8_script = ScriptCompiler::CompileUnbound( + env->isolate(), + &source, + compile_options); + + if (v8_script.IsEmpty()) { + if (display_errors) { + DecorateErrorStack(env, try_catch); + } + try_catch.ReThrow(); + return; + } + contextify_script->script_.Reset(env->isolate(), v8_script); + + if (compile_options == ScriptCompiler::kConsumeCodeCache) { + args.This()->Set( + env->cached_data_rejected_string(), + Boolean::New(env->isolate(), source.GetCachedData()->rejected)); + } else if (compile_options == ScriptCompiler::kProduceCodeCache) { + const ScriptCompiler::CachedData* cached_data = source.GetCachedData(); + MaybeLocal buf = Buffer::Copy( + env, + reinterpret_cast(cached_data->data), + cached_data->length); + args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); + } +} - if (!args.IsConstructCall()) { - return env->ThrowError("Must call vm.Script as a constructor."); - } - ContextifyScript* contextify_script = - new ContextifyScript(env, args.This()); +bool ContextifyScript::InstanceOf(Environment* env, const Local& value) { + return !value.IsEmpty() && + env->script_context_constructor_template()->HasInstance(value); +} - TryCatch try_catch; - Local code = args[0]->ToString(env->isolate()); - Local filename = GetFilenameArg(args, 1); - Local lineOffset = GetLineOffsetArg(args, 1); - Local columnOffset = GetColumnOffsetArg(args, 1); - bool display_errors = GetDisplayErrorsArg(args, 1); - MaybeLocal cached_data_buf = GetCachedData(env, args, 1); - bool produce_cached_data = GetProduceCachedData(env, args, 1); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } - ScriptCompiler::CachedData* cached_data = nullptr; - if (!cached_data_buf.IsEmpty()) { - Local ui8 = cached_data_buf.ToLocalChecked(); - ArrayBuffer::Contents contents = ui8->Buffer()->GetContents(); - cached_data = new ScriptCompiler::CachedData( - static_cast(contents.Data()) + ui8->ByteOffset(), - ui8->ByteLength()); - } + // args: [options] +void ContextifyScript::RunInThisContext( + const FunctionCallbackInfo& args) { + // Assemble arguments + TryCatch try_catch; + uint64_t timeout = GetTimeoutArg(args, 0); + bool display_errors = GetDisplayErrorsArg(args, 0); + if (try_catch.HasCaught()) { + try_catch.ReThrow(); + return; + } + + // Do the eval within this context + Environment* env = Environment::GetCurrent(args); + EvalMachine(env, timeout, display_errors, args, try_catch); +} - ScriptOrigin origin(filename, lineOffset, columnOffset); - ScriptCompiler::Source source(code, origin, cached_data); - ScriptCompiler::CompileOptions compile_options = - ScriptCompiler::kNoCompileOptions; + // args: sandbox, [options] +void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); - if (source.GetCachedData() != nullptr) - compile_options = ScriptCompiler::kConsumeCodeCache; - else if (produce_cached_data) - compile_options = ScriptCompiler::kProduceCodeCache; + int64_t timeout; + bool display_errors; - Local v8_script = ScriptCompiler::CompileUnbound( - env->isolate(), - &source, - compile_options); + // Assemble arguments + if (!args[0]->IsObject()) { + return env->ThrowTypeError( + "contextifiedSandbox argument must be an object."); + } - if (v8_script.IsEmpty()) { - if (display_errors) { - DecorateErrorStack(env, try_catch); - } + Local sandbox = args[0].As(); + { + TryCatch try_catch; + timeout = GetTimeoutArg(args, 1); + display_errors = GetDisplayErrorsArg(args, 1); + if (try_catch.HasCaught()) { try_catch.ReThrow(); return; } - contextify_script->script_.Reset(env->isolate(), v8_script); - - if (compile_options == ScriptCompiler::kConsumeCodeCache) { - args.This()->Set( - env->cached_data_rejected_string(), - Boolean::New(env->isolate(), source.GetCachedData()->rejected)); - } else if (compile_options == ScriptCompiler::kProduceCodeCache) { - const ScriptCompiler::CachedData* cached_data = source.GetCachedData(); - MaybeLocal buf = Buffer::Copy( - env, - reinterpret_cast(cached_data->data), - cached_data->length); - args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); - } } - - static bool InstanceOf(Environment* env, const Local& value) { - return !value.IsEmpty() && - env->script_context_constructor_template()->HasInstance(value); + // Get the context from the sandbox + ContextifyContext* contextify_context = + ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); + if (contextify_context == nullptr) { + return env->ThrowTypeError( + "sandbox argument must have been converted to a context."); } + if (contextify_context->context().IsEmpty()) + return; - // args: [options] - static void RunInThisContext(const FunctionCallbackInfo& args) { - // Assemble arguments + { TryCatch try_catch; - uint64_t timeout = GetTimeoutArg(args, 0); - bool display_errors = GetDisplayErrorsArg(args, 0); + // Do the eval within the context + Context::Scope context_scope(contextify_context->context()); + if (EvalMachine(contextify_context->env(), + timeout, + display_errors, + args, + try_catch)) { + contextify_context->CopyProperties(); + } + if (try_catch.HasCaught()) { try_catch.ReThrow(); return; } - - // Do the eval within this context - Environment* env = Environment::GetCurrent(args); - EvalMachine(env, timeout, display_errors, args, try_catch); } +} - // args: sandbox, [options] - static void RunInContext(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - int64_t timeout; - bool display_errors; - - // Assemble arguments - if (!args[0]->IsObject()) { - return env->ThrowTypeError( - "contextifiedSandbox argument must be an object."); - } +void ContextifyScript::DecorateErrorStack(Environment* env, + const TryCatch& try_catch) { + Local exception = try_catch.Exception(); - Local sandbox = args[0].As(); - { - TryCatch try_catch; - timeout = GetTimeoutArg(args, 1); - display_errors = GetDisplayErrorsArg(args, 1); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } - } + if (!exception->IsObject()) + return; - // Get the context from the sandbox - ContextifyContext* contextify_context = - ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); - if (contextify_context == nullptr) { - return env->ThrowTypeError( - "sandbox argument must have been converted to a context."); - } + Local err_obj = exception.As(); - if (contextify_context->context().IsEmpty()) - return; + if (IsExceptionDecorated(env, err_obj)) + return; - { - TryCatch try_catch; - // Do the eval within the context - Context::Scope context_scope(contextify_context->context()); - if (EvalMachine(contextify_context->env(), - timeout, - display_errors, - args, - try_catch)) { - contextify_context->CopyProperties(); - } + AppendExceptionLine(env, exception, try_catch.Message()); + Local stack = err_obj->Get(env->stack_string()); + auto maybe_value = + err_obj->GetPrivate( + env->context(), + env->arrow_message_private_symbol()); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } - } + Local arrow; + if (!(maybe_value.ToLocal(&arrow) && + arrow->IsString() && + stack->IsString())) { + return; } - static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) { - Local exception = try_catch.Exception(); - - if (!exception->IsObject()) - return; - - Local err_obj = exception.As(); - - if (IsExceptionDecorated(env, err_obj)) - return; - - AppendExceptionLine(env, exception, try_catch.Message()); - Local stack = err_obj->Get(env->stack_string()); - auto maybe_value = - err_obj->GetPrivate( - env->context(), - env->arrow_message_private_symbol()); + Local decorated_stack = String::Concat(arrow.As(), + stack.As()); + err_obj->Set(env->stack_string(), decorated_stack); + err_obj->SetPrivate( + env->context(), + env->decorated_private_symbol(), + True(env->isolate())); +} - Local arrow; - if (!(maybe_value.ToLocal(&arrow) && - arrow->IsString() && - stack->IsString())) { - return; - } +int64_t ContextifyScript::GetTimeoutArg(const FunctionCallbackInfo& args, + const int i) { + if (args[i]->IsUndefined() || args[i]->IsString()) { + return -1; + } + if (!args[i]->IsObject()) { + Environment::ThrowTypeError(args.GetIsolate(), + "options must be an object"); + return -1; + } - Local decorated_stack = String::Concat(arrow.As(), - stack.As()); - err_obj->Set(env->stack_string(), decorated_stack); - err_obj->SetPrivate( - env->context(), - env->decorated_private_symbol(), - True(env->isolate())); + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "timeout"); + Local value = args[i].As()->Get(key); + if (value->IsUndefined()) { + return -1; } + int64_t timeout = value->IntegerValue(); - static int64_t GetTimeoutArg(const FunctionCallbackInfo& args, - const int i) { - if (args[i]->IsUndefined() || args[i]->IsString()) { - return -1; - } - if (!args[i]->IsObject()) { - Environment::ThrowTypeError(args.GetIsolate(), - "options must be an object"); - return -1; - } + if (timeout <= 0) { + Environment::ThrowRangeError(args.GetIsolate(), + "timeout must be a positive number"); + return -1; + } + return timeout; +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "timeout"); - Local value = args[i].As()->Get(key); - if (value->IsUndefined()) { - return -1; - } - int64_t timeout = value->IntegerValue(); - if (timeout <= 0) { - Environment::ThrowRangeError(args.GetIsolate(), - "timeout must be a positive number"); - return -1; - } - return timeout; +bool ContextifyScript::GetDisplayErrorsArg( + const FunctionCallbackInfo& args, const int i) { + if (args[i]->IsUndefined() || args[i]->IsString()) { + return true; + } + if (!args[i]->IsObject()) { + Environment::ThrowTypeError(args.GetIsolate(), + "options must be an object"); + return false; } + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), + "displayErrors"); + Local value = args[i].As()->Get(key); - static bool GetDisplayErrorsArg(const FunctionCallbackInfo& args, - const int i) { - if (args[i]->IsUndefined() || args[i]->IsString()) { - return true; - } - if (!args[i]->IsObject()) { - Environment::ThrowTypeError(args.GetIsolate(), - "options must be an object"); - return false; - } + return value->IsUndefined() ? true : value->BooleanValue(); +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), - "displayErrors"); - Local value = args[i].As()->Get(key); - return value->IsUndefined() ? true : value->BooleanValue(); - } +Local ContextifyScript::GetFilenameArg( + const FunctionCallbackInfo& args, const int i) { + Local defaultFilename = + FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine."); + if (args[i]->IsUndefined()) { + return defaultFilename; + } + if (args[i]->IsString()) { + return args[i].As(); + } + if (!args[i]->IsObject()) { + Environment::ThrowTypeError(args.GetIsolate(), + "options must be an object"); + return Local(); + } - static Local GetFilenameArg(const FunctionCallbackInfo& args, - const int i) { - Local defaultFilename = - FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine."); + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename"); + Local value = args[i].As()->Get(key); - if (args[i]->IsUndefined()) { - return defaultFilename; - } - if (args[i]->IsString()) { - return args[i].As(); - } - if (!args[i]->IsObject()) { - Environment::ThrowTypeError(args.GetIsolate(), - "options must be an object"); - return Local(); - } + if (value->IsUndefined()) + return defaultFilename; + return value->ToString(args.GetIsolate()); +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename"); - Local value = args[i].As()->Get(key); - if (value->IsUndefined()) - return defaultFilename; - return value->ToString(args.GetIsolate()); +MaybeLocal ContextifyScript::GetCachedData( + Environment* env, + const FunctionCallbackInfo& args, + const int i) { + if (!args[i]->IsObject()) { + return MaybeLocal(); + } + Local value = args[i].As()->Get(env->cached_data_string()); + if (value->IsUndefined()) { + return MaybeLocal(); } + if (!value->IsUint8Array()) { + Environment::ThrowTypeError( + args.GetIsolate(), + "options.cachedData must be a Buffer instance"); + return MaybeLocal(); + } - static MaybeLocal GetCachedData( - Environment* env, - const FunctionCallbackInfo& args, - const int i) { - if (!args[i]->IsObject()) { - return MaybeLocal(); - } - Local value = args[i].As()->Get(env->cached_data_string()); - if (value->IsUndefined()) { - return MaybeLocal(); - } + return value.As(); +} - if (!value->IsUint8Array()) { - Environment::ThrowTypeError( - args.GetIsolate(), - "options.cachedData must be a Buffer instance"); - return MaybeLocal(); - } - return value.As(); +bool ContextifyScript::GetProduceCachedData( + Environment* env, + const FunctionCallbackInfo& args, + const int i) { + if (!args[i]->IsObject()) { + return false; } + Local value = + args[i].As()->Get(env->produce_cached_data_string()); + return value->IsTrue(); +} - static bool GetProduceCachedData( - Environment* env, - const FunctionCallbackInfo& args, - const int i) { - if (!args[i]->IsObject()) { - return false; - } - Local value = - args[i].As()->Get(env->produce_cached_data_string()); - return value->IsTrue(); +Local ContextifyScript::GetLineOffsetArg( + const FunctionCallbackInfo& args, + const int i) { + Local defaultLineOffset = Integer::New(args.GetIsolate(), 0); + + if (!args[i]->IsObject()) { + return defaultLineOffset; } + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "lineOffset"); + Local value = args[i].As()->Get(key); - static Local GetLineOffsetArg( - const FunctionCallbackInfo& args, - const int i) { - Local defaultLineOffset = Integer::New(args.GetIsolate(), 0); + return value->IsUndefined() ? defaultLineOffset : value->ToInteger(); +} - if (!args[i]->IsObject()) { - return defaultLineOffset; - } - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "lineOffset"); - Local value = args[i].As()->Get(key); +Local ContextifyScript::GetColumnOffsetArg( + const FunctionCallbackInfo& args, + const int i) { + Local defaultColumnOffset = Integer::New(args.GetIsolate(), 0); - return value->IsUndefined() ? defaultLineOffset : value->ToInteger(); + if (!args[i]->IsObject()) { + return defaultColumnOffset; } + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), + "columnOffset"); + Local value = args[i].As()->Get(key); - static Local GetColumnOffsetArg( - const FunctionCallbackInfo& args, - const int i) { - Local defaultColumnOffset = Integer::New(args.GetIsolate(), 0); - - if (!args[i]->IsObject()) { - return defaultColumnOffset; - } + return value->IsUndefined() ? defaultColumnOffset : value->ToInteger(); +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), - "columnOffset"); - Local value = args[i].As()->Get(key); - return value->IsUndefined() ? defaultColumnOffset : value->ToInteger(); +bool ContextifyScript::EvalMachine(Environment* env, + const int64_t timeout, + const bool display_errors, + const FunctionCallbackInfo& args, + TryCatch& try_catch) { + if (!env->CanCallIntoJs()) + return false; + if (!ContextifyScript::InstanceOf(env, args.Holder())) { + env->ThrowTypeError( + "Script methods can only be called on script instances."); + return false; } + ContextifyScript* wrapped_script = Unwrap(args.Holder()); + Local unbound_script = + PersistentToLocal(env->isolate(), wrapped_script->script_); + Local