diff --git a/LICENSE b/LICENSE index 753f9c7e20910c..5830b6ff88fe83 100644 --- a/LICENSE +++ b/LICENSE @@ -1041,4 +1041,35 @@ The externally maintained libraries used by Node.js are: on the database or the code. Any person making a contribution to the database or code waives all rights to future claims in that contribution or in the TZ Database. + +- 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 3d6d6f7686679a..713093e38cf82c 100644 --- a/Makefile +++ b/Makefile @@ -182,6 +182,12 @@ test-timers: test-timers-clean: $(MAKE) --directory=tools clean +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 7819816bbbd2f5..23e9b552a16123 100644 --- a/common.gypi +++ b/common.gypi @@ -264,7 +264,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 @@ -275,7 +275,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', @@ -302,7 +302,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..e90dac122f8cfb --- /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 0bc27ae4f63b16..66b8ab0905d37c 100644 --- a/node.gyp +++ b/node.gyp @@ -73,6 +73,7 @@ 'lib/internal/socket_list.js', 'lib/internal/repl.js', 'lib/internal/util.js', + 'lib/worker.js' ], }, @@ -118,6 +119,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', @@ -130,6 +133,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', @@ -143,6 +147,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', @@ -152,7 +157,10 @@ 'src/node_watchdog.h', 'src/node_wrap.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', @@ -166,6 +174,7 @@ 'src/util.h', 'src/util-inl.h', 'src/util.cc', + 'src/worker.h', 'deps/http_parser/http_parser.h', 'deps/v8/include/v8.h', 'deps/v8/include/v8-debug.h', @@ -184,6 +193,11 @@ 'V8_DEPRECATION_WARNINGS=1', ], + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '-stdlib=libc++', + ], + }, 'conditions': [ [ 'node_tag!=""', { @@ -641,7 +655,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 37de0588b2af14..7c6bded271c7ba 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -162,6 +162,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 context = object(); @@ -181,7 +183,7 @@ Local AsyncWrap::MakeCallback(const Local cb, } } - TryCatch try_catch; + TryCatch try_catch(env()->isolate()); try_catch.SetVerbose(true); if (has_domain) { @@ -196,6 +198,8 @@ Local AsyncWrap::MakeCallback(const Local cb, if (has_async_queue()) { try_catch.SetVerbose(false); env()->async_hooks_pre_function()->Call(context, 0, nullptr); + if (try_catch.HasTerminated()) + return Undefined(env()->isolate()); if (try_catch.HasCaught()) FatalError("node::AsyncWrap::MakeCallback", "pre hook threw"); try_catch.SetVerbose(true); @@ -224,6 +228,8 @@ Local AsyncWrap::MakeCallback(const Local cb, if (has_async_queue()) { try_catch.SetVerbose(false); env()->async_hooks_post_function()->Call(context, 0, nullptr); + if (try_catch.HasTerminated()) + return Undefined(env()->isolate()); if (try_catch.HasCaught()) FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); try_catch.SetVerbose(true); diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 4ef5e01e77058a..fc367a8c19ceca 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1239,13 +1239,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)); @@ -1254,10 +1260,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 cbc8c4ff1f5eaf..584d4f2c52d95a 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 @@ -44,6 +45,14 @@ inline Environment::IsolateData::IsolateData(v8::Isolate* isolate, PropertyName ## _(isolate, FIXED_ONE_BYTE_STRING(isolate, StringValue)), 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 { @@ -132,8 +141,17 @@ inline void Environment::TickInfo::set_last_threw(bool value) { } 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; } @@ -168,7 +186,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)), @@ -178,6 +197,7 @@ inline Environment::Environment(v8::Local context, printed_error_(false), trace_sync_io_(false), 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. @@ -191,8 +211,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(); @@ -202,6 +226,27 @@ inline Environment::~Environment() { delete[] heap_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() { @@ -264,10 +309,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) { @@ -349,6 +402,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); } @@ -370,6 +462,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_; } @@ -464,6 +560,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; + } +} + #define V(PropertyName, StringValue) \ inline \ v8::Local Environment::IsolateData::PropertyName() const { \ @@ -480,6 +625,21 @@ inline void Environment::SetTemplateMethod(v8::Local that, PER_ISOLATE_STRING_PROPERTIES(V) #undef V +#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 ## _); \ @@ -492,6 +652,7 @@ inline void Environment::SetTemplateMethod(v8::Local that, #undef ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES #undef PER_ISOLATE_STRING_PROPERTIES +#undef PER_ISOLATE_SYMBOL_PROPERTIES } // namespace node diff --git a/src/env.h b/src/env.h index ce972d598edf99..6b81ac95a9304b 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 @@ -85,6 +87,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") \ @@ -253,6 +256,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 @@ -266,6 +290,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 { @@ -339,26 +382,6 @@ class Environment { DISALLOW_COPY_AND_ASSIGN(TickInfo); }; - 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( @@ -370,7 +393,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(); @@ -393,10 +417,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(); @@ -408,6 +433,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_abort_on_uncaught_exc() const; inline void set_using_abort_on_uncaught_exc(bool value); @@ -432,6 +458,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); @@ -466,6 +505,18 @@ class Environment { const char* name, v8::FunctionCallback callback); + 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 are shared across shared contexts. The getters simply proxy to // the per-isolate primitive. @@ -480,6 +531,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_; } @@ -497,12 +553,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_; @@ -519,13 +579,18 @@ class Environment { bool using_asyncwrap_; bool printed_error_; bool trace_sync_io_; + 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; @@ -549,6 +614,11 @@ class Environment { PER_ISOLATE_STRING_PROPERTIES(V) #undef V +#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); @@ -562,6 +632,11 @@ class Environment { PER_ISOLATE_STRING_PROPERTIES(V) #undef V +#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 0d0096cab733f5..eccae8a7ee5fec 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..14cf38715f84cf --- /dev/null +++ b/src/node-contextify.h @@ -0,0 +1,42 @@ +#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 v8::Local GetFilenameArg( + 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 b60cfb573ceead..5e72bc6ed9947d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -42,6 +42,7 @@ #include "v8-debug.h" #include "v8-profiler.h" #include "zlib.h" +#include "worker.h" #include #include // PATH_MAX @@ -142,14 +143,22 @@ static const char* icu_data_dir = nullptr; // used by C++ modules as well bool no_deprecation = false; +bool experimental_workers = 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 uv_thread_t process_main_thread; static Isolate* node_isolate = nullptr; -static v8::Platform* default_platform; + +static size_t thread_id_counter = 1; +static uv_mutex_t thread_id_counter_mutex; static void CheckImmediate(uv_check_t* handle) { @@ -994,6 +1003,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()); @@ -1019,7 +1030,7 @@ Local MakeCallback(Environment* env, } } - TryCatch try_catch; + TryCatch try_catch(env->isolate()); try_catch.SetVerbose(true); if (has_domain) { @@ -1034,6 +1045,8 @@ Local MakeCallback(Environment* env, if (has_async_queue) { try_catch.SetVerbose(false); env->async_hooks_pre_function()->Call(object, 0, nullptr); + if (try_catch.HasTerminated()) + return Undefined(env->isolate()); if (try_catch.HasCaught()) FatalError("node::MakeCallback", "pre hook threw"); try_catch.SetVerbose(true); @@ -1041,9 +1054,16 @@ Local MakeCallback(Environment* env, Local ret = callback->Call(recv, argc, argv); + if (try_catch.HasTerminated()) { + CHECK(!env->CanCallIntoJs()); + return Undefined(env->isolate()); + } + if (has_async_queue) { try_catch.SetVerbose(false); env->async_hooks_post_function()->Call(object, 0, nullptr); + if (try_catch.HasTerminated()) + return Undefined(env->isolate()); if (try_catch.HasCaught()) FatalError("node::MakeCallback", "post hook threw"); try_catch.SetVerbose(true); @@ -1368,6 +1388,7 @@ void AppendExceptionLine(Environment* env, if (env->printed_error()) return; env->set_printed_error(true); + ScopedLock::Mutex lock(&process_mutex); uv_tty_reset_mode(); fprintf(stderr, "\n%s", arrow); } @@ -1462,13 +1483,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); @@ -1520,6 +1543,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."); @@ -1563,6 +1587,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 { @@ -1720,6 +1746,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"); @@ -1739,6 +1766,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"); @@ -1758,6 +1786,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"); @@ -1777,6 +1806,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"); @@ -1914,7 +1944,7 @@ static void InitGroups(const FunctionCallbackInfo& args) { void Exit(const FunctionCallbackInfo& args) { - exit(args[0]->Int32Value()); + Environment::GetCurrent(args)->Exit(args[0]->Int32Value()); } @@ -1923,6 +1953,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)); @@ -2157,7 +2189,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; @@ -2172,12 +2204,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); } } @@ -2199,6 +2231,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); @@ -2294,7 +2329,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)); } @@ -2303,7 +2343,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); } @@ -2311,6 +2353,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); @@ -2339,6 +2382,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); @@ -2360,6 +2404,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)) @@ -2386,6 +2431,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; @@ -2407,6 +2453,7 @@ static void EnvDeleter(Local property, static void EnvEnumerator(const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); + ScopedLock::Read lock(&process_rwlock); #ifdef __POSIX__ int size = 0; while (environ[size]) @@ -2500,12 +2547,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); } @@ -2513,6 +2564,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(); } @@ -2619,9 +2671,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", @@ -2763,15 +2825,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(), @@ -2779,53 +2844,54 @@ void SetupProcessObject(Environment* env, NeedImmediateCallbackSetter, env->as_external()); - // -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())); - } + if (env->is_main_thread()) { + // -e, --eval + if (eval_string) { + READONLY_PROPERTY(process, + "_eval", + String::NewFromUtf8(env->isolate(), eval_string)); + } - // -i, --interactive - if (force_repl) { - READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); - } + // -p, --print + if (print_eval) { + READONLY_PROPERTY(process, "_print_eval", 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); + // -i, --interactive + if (force_repl) { + READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); } - 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); + } + READONLY_PROPERTY(process, + "_preload_modules", + array); + delete[] preload_modules; + preload_modules = nullptr; + preload_module_count = 0; + } - // --no-deprecation - if (no_deprecation) { - READONLY_PROPERTY(process, "noDeprecation", True(env->isolate())); - } + // --no-deprecation + if (no_deprecation) { + READONLY_PROPERTY(process, "noDeprecation", True(env->isolate())); + } - // --throw-deprecation - if (throw_deprecation) { - READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); - } + // --throw-deprecation + if (throw_deprecation) { + READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); + } - // --trace-deprecation - if (trace_deprecation) { - READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); + // --trace-deprecation + if (trace_deprecation) { + READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); + } } size_t exec_path_len = 2 * PATH_MAX; @@ -2844,50 +2910,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); @@ -2905,6 +2977,7 @@ void SetupProcessObject(Environment* env, #undef READONLY_PROPERTY +#undef READONLY_DONT_ENUM_PROPERTY static void AtExit() { @@ -2949,8 +3022,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 @@ -2962,7 +3033,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); @@ -3205,6 +3276,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; @@ -3523,6 +3597,7 @@ static void DebugEnd(const FunctionCallbackInfo& args) { inline void PlatformInit() { +process_main_thread = uv_thread_self(); #ifdef __POSIX__ sigset_t sigmask; sigemptyset(&sigmask); @@ -3593,6 +3668,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())); @@ -3684,6 +3760,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; @@ -3763,18 +3842,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); @@ -3782,7 +3849,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; @@ -3790,6 +3857,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, @@ -3801,7 +3889,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()); @@ -3827,24 +3934,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")); @@ -3854,38 +3960,60 @@ 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; params.array_buffer_allocator = &array_buffer_allocator; - Isolate* isolate = Isolate::New(params); - if (track_heap_objects) { - isolate->GetHeapProfiler()->StartTrackingHeapObjects(true); - } - // 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()) - node_isolate = isolate; + node_isolate = Isolate::New(params); + if (track_heap_objects) + node_isolate->GetHeapProfiler()->StartTrackingHeapObjects(true); + + 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); + Locker locker(node_isolate); + Isolate::Scope isolate_scope(node_isolate); + HandleScope handle_scope(node_isolate); + Local context = Context::New(node_isolate); + Environment* env = CreateEnvironment(node_isolate, + uv_default_loop(), + context, + argc, + argv, + exec_argc, + exec_argv); Context::Scope context_scope(context); - if (instance_data->is_main()) - env->set_using_abort_on_uncaught_exc(abort_on_uncaught_exception); + env->set_using_abort_on_uncaught_exc(abort_on_uncaught_exception); // Start debug agent when argv has --debug - if (instance_data->use_debug_agent()) + if (use_debug_agent) StartDebug(env, debug_wait_connect); LoadEnvironment(env); @@ -3893,18 +4021,19 @@ 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); { - SealHandleScope seal(isolate); + SealHandleScope seal(node_isolate); bool more; do { - v8::platform::PumpMessageLoop(default_platform, isolate); + v8::platform::PumpMessageLoop(default_platform, node_isolate); more = uv_run(env->event_loop(), UV_RUN_ONCE); + CleanupWorkerContexts(); if (more == false) { - v8::platform::PumpMessageLoop(default_platform, isolate); + v8::platform::PumpMessageLoop(default_platform, node_isolate); EmitBeforeExit(env); // Emit `beforeExit` if the loop became alive either after emitting @@ -3917,10 +4046,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) @@ -3931,14 +4059,31 @@ static void StartNodeInstance(void* arg) { env = nullptr; } - CHECK_NE(isolate, nullptr); - isolate->Dispose(); - isolate = nullptr; - if (instance_data->is_main()) - node_isolate = nullptr; + CHECK_NE(node_isolate, nullptr); + node_isolate->Dispose(); + node_isolate = nullptr; + 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); @@ -3963,25 +4108,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 bd91fb888ae046..bed43490826d68 100644 --- a/src/node.js +++ b/src/node.js @@ -38,7 +38,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(); @@ -49,8 +49,25 @@ // 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'); - if (NativeModule.exists('_third_party_main')) { + if (!process._eval) { + var path = NativeModule.require('path'); + process.argv[1] = path.resolve(process.argv[1]); + var Module = NativeModule.require('module'); + } + + 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. @@ -213,6 +230,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; @@ -645,6 +666,7 @@ }); process.__defineGetter__('stdin', function() { + if (process.isWorkerThread) return null; if (stdin) return stdin; var tty_wrap = process.binding('tty_wrap'); @@ -720,10 +742,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() { @@ -796,7 +820,6 @@ var errnoException = NativeModule.require('util')._errnoException; throw errnoException(err, 'uv_signal_start'); } - signalWraps[type] = wrap; } }); @@ -809,7 +832,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 6520355239ea84..d01f06912a3388 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" @@ -468,268 +470,277 @@ 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()); + if (!args.IsConstructCall()) { + return env->ThrowError("Must call vm.Script as a constructor."); + } - TryCatch try_catch; - Local code = args[0]->ToString(env->isolate()); - Local filename = GetFilenameArg(args, 1); - bool display_errors = GetDisplayErrorsArg(args, 1); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } + 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); + bool display_errors = GetDisplayErrorsArg(args, 1); + if (try_catch.HasCaught()) { + try_catch.ReThrow(); + return; + } - ScriptOrigin origin(filename); - ScriptCompiler::Source source(code, origin); - Local v8_script = - ScriptCompiler::CompileUnbound(env->isolate(), &source); + ScriptOrigin origin(filename); + ScriptCompiler::Source source(code, origin); + Local v8_script = + ScriptCompiler::CompileUnbound(env->isolate(), &source); - if (v8_script.IsEmpty()) { - if (display_errors) { - AppendExceptionLine(env, try_catch.Exception(), try_catch.Message()); - } - try_catch.ReThrow(); - return; + if (v8_script.IsEmpty()) { + if (display_errors) { + AppendExceptionLine(env, try_catch.Exception(), try_catch.Message()); } - contextify_script->script_.Reset(env->isolate(), v8_script); + try_catch.ReThrow(); + return; } + contextify_script->script_.Reset(env->isolate(), v8_script); +} - static bool InstanceOf(Environment* env, const Local& value) { - return !value.IsEmpty() && - env->script_context_constructor_template()->HasInstance(value); - } +bool ContextifyScript::InstanceOf(Environment* env, const Local& value) { + return !value.IsEmpty() && + env->script_context_constructor_template()->HasInstance(value); +} // args: [options] - static void RunInThisContext(const FunctionCallbackInfo& args) { - // Assemble arguments +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); +} + + // args: sandbox, [options] +void ContextifyScript::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."); + } + + Local sandbox = args[0].As(); + { TryCatch try_catch; - uint64_t timeout = GetTimeoutArg(args, 0); - bool display_errors = GetDisplayErrorsArg(args, 0); + timeout = GetTimeoutArg(args, 1); + display_errors = GetDisplayErrorsArg(args, 1); 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."); - } + // Get the context from the sandbox + ContextifyContext* contextify_context = + ContextifyContext::ContextFromContextifiedSandbox(env->isolate(), + sandbox); + if (contextify_context == nullptr) { + return env->ThrowTypeError( + "sandbox argument must have been converted to a context."); + } - 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 (contextify_context->context().IsEmpty()) + return; - // Get the context from the sandbox - ContextifyContext* contextify_context = - ContextifyContext::ContextFromContextifiedSandbox(env->isolate(), - sandbox); - if (contextify_context == nullptr) { - return env->ThrowTypeError( - "sandbox argument must have been converted to a context."); + { + 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(); } - if (contextify_context->context().IsEmpty()) + if (try_catch.HasCaught()) { + try_catch.ReThrow(); 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(); - } - - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } } } +} - 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; - } - - 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; +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 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 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; - } + 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(), - "displayErrors"); - Local value = args[i].As()->Get(key); - return value->IsUndefined() ? true : value->BooleanValue(); +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 Local GetFilenameArg(const FunctionCallbackInfo& args, - const int i) { - Local defaultFilename = - FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine."); + return value->IsUndefined() ? true : value->BooleanValue(); +} - 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(); - } - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename"); - Local value = args[i].As()->Get(key); +Local ContextifyScript::GetFilenameArg( + const FunctionCallbackInfo& args, const int i) { + Local defaultFilename = + FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine."); - if (value->IsUndefined()) - return defaultFilename; - return value->ToString(args.GetIsolate()); + 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(); } + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename"); + Local value = args[i].As()->Get(key); - static bool EvalMachine(Environment* env, - const int64_t timeout, - const bool display_errors, - const FunctionCallbackInfo& args, - TryCatch& try_catch) { - if (!ContextifyScript::InstanceOf(env, args.Holder())) { - env->ThrowTypeError( - "Script methods can only be called on script instances."); - return false; - } + if (value->IsUndefined()) + return defaultFilename; + return value->ToString(args.GetIsolate()); +} - ContextifyScript* wrapped_script = Unwrap(args.Holder()); - Local unbound_script = - PersistentToLocal(env->isolate(), wrapped_script->script_); - Local