diff --git a/Makefile b/Makefile index fb854603d8..a42d392073 100644 --- a/Makefile +++ b/Makefile @@ -406,7 +406,7 @@ CPPLINT_EXCLUDE += src/queue.h CPPLINT_EXCLUDE += src/tree.h CPPLINT_EXCLUDE += src/v8abbr.h -CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard src/*.cc src/*.h src/*.c tools/icu/*.h tools/icu/*.cc)) +CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard src/*.cc src/*.h src/*.c tools/icu/*.h tools/icu/*.cc deps/debugger-agent/include/* deps/debugger-agent/src/*)) cpplint: @$(PYTHON) tools/cpplint.py $(CPPLINT_FILES) diff --git a/deps/debugger-agent/debugger-agent.gyp b/deps/debugger-agent/debugger-agent.gyp new file mode 100644 index 0000000000..e98206849a --- /dev/null +++ b/deps/debugger-agent/debugger-agent.gyp @@ -0,0 +1,24 @@ +{ + "targets": [{ + "target_name": "debugger-agent", + "type": "<(library)", + "include_dirs": [ + "src", + "include", + "../v8/include", + "../uv/include", + + # Private node.js folder and stuff needed to include from it + "../../src", + "../cares/include", + ], + "direct_dependent_settings": { + "include_dirs": [ + "include", + ], + }, + "sources": [ + "src/agent.cc", + ], + }], +} diff --git a/deps/debugger-agent/include/debugger-agent.h b/deps/debugger-agent/include/debugger-agent.h new file mode 100644 index 0000000000..762a687a0a --- /dev/null +++ b/deps/debugger-agent/include/debugger-agent.h @@ -0,0 +1,109 @@ +// Copyright Fedor Indutny and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef DEPS_DEBUGGER_AGENT_INCLUDE_DEBUGGER_AGENT_H_ +#define DEPS_DEBUGGER_AGENT_INCLUDE_DEBUGGER_AGENT_H_ + +#include "uv.h" +#include "v8.h" +#include "v8-debug.h" + +namespace node { + +// Forward declaration +class Environment; + +namespace debugger { + +// Forward declaration +class AgentMessage; + +class Agent { + public: + explicit Agent(node::Environment* env); + ~Agent(); + + typedef void (*DispatchHandler)(node::Environment* env); + + // Start the debugger agent thread + bool Start(int port, bool wait); + // Listen for debug events + void Enable(); + // Stop the debugger agent + void Stop(); + + inline void set_dispatch_handler(DispatchHandler handler) { + dispatch_handler_ = handler; + } + + inline node::Environment* parent_env() const { return parent_env_; } + inline node::Environment* child_env() const { return child_env_; } + + protected: + void InitAdaptor(Environment* env); + + // Worker body + void WorkerRun(); + + static void ThreadCb(Agent* agent); + static void ParentSignalCb(uv_async_t* signal); + static void ChildSignalCb(uv_async_t* signal); + static void MessageHandler(const v8::Debug::Message& message); + + // V8 API + static Agent* Unwrap(const v8::FunctionCallbackInfo& args); + static void NotifyListen(const v8::FunctionCallbackInfo& args); + static void NotifyWait(const v8::FunctionCallbackInfo& args); + static void SendCommand(const v8::FunctionCallbackInfo& args); + + void EnqueueMessage(AgentMessage* message); + + enum State { + kNone, + kRunning + }; + + // TODO(indutny): Verify that there are no races + State state_; + + int port_; + bool wait_; + + uv_sem_t start_sem_; + uv_mutex_t message_mutex_; + uv_async_t child_signal_; + + uv_thread_t thread_; + node::Environment* parent_env_; + node::Environment* child_env_; + uv_loop_t child_loop_; + v8::Persistent api_; + + // QUEUE + void* messages_[2]; + + DispatchHandler dispatch_handler_; +}; + +} // namespace debugger +} // namespace node + +#endif // DEPS_DEBUGGER_AGENT_INCLUDE_DEBUGGER_AGENT_H_ diff --git a/deps/debugger-agent/lib/_debugger_agent.js b/deps/debugger-agent/lib/_debugger_agent.js new file mode 100644 index 0000000000..680c5e95c4 --- /dev/null +++ b/deps/debugger-agent/lib/_debugger_agent.js @@ -0,0 +1,191 @@ +var assert = require('assert'); +var net = require('net'); +var util = require('util'); +var Buffer = require('buffer').Buffer; + +var Transform = require('stream').Transform; + +exports.start = function start() { + var agent = new Agent(); + + // Do not let `agent.listen()` request listening from cluster master + var cluster = require('cluster'); + cluster.isWorker = false; + cluster.isMaster = true; + + agent.on('error', function(err) { + process._rawDebug(err.stack || err); + }); + + agent.listen(process._debugAPI.port, function() { + var addr = this.address(); + process._rawDebug('Debugger listening on port %d', addr.port); + process._debugAPI.notifyListen(); + }); + + // Just to spin-off events + // TODO(indutny): Figure out why node.cc isn't doing this + setImmediate(function() { + }); + + process._debugAPI.onclose = function() { + // We don't care about it, but it prevents loop from cleaning up gently + // NOTE: removeAllListeners won't work, as it doesn't call `removeListener` + process.listeners('SIGWINCH').forEach(function(fn) { + process.removeListener('SIGWINCH', fn); + }); + + agent.close(); + }; + + // Not used now, but anyway + return agent; +}; + +function Agent() { + net.Server.call(this, this.onConnection); + + this.first = true; + this.binding = process._debugAPI; + + var self = this; + this.binding.onmessage = function(msg) { + self.clients.forEach(function(client) { + client.send({}, msg); + }); + }; + + this.clients = []; + assert(this.binding, 'Debugger agent running without bindings!'); +} +util.inherits(Agent, net.Server); + +Agent.prototype.onConnection = function onConnection(socket) { + var c = new Client(this, socket); + + c.start(); + this.clients.push(c); + + var self = this; + c.once('close', function() { + var index = self.clients.indexOf(c); + assert(index !== -1); + self.clients.splice(index, 1); + }); +}; + +Agent.prototype.notifyWait = function notifyWait() { + if (this.first) + this.binding.notifyWait(); + this.first = false; +}; + +function Client(agent, socket) { + Transform.call(this); + this._readableState.objectMode = true; + + this.agent = agent; + this.binding = this.agent.binding; + this.socket = socket; + + // Parse incoming data + this.state = 'headers'; + this.headers = {}; + this.buffer = ''; + socket.pipe(this); + + this.on('data', this.onCommand); + + var self = this; + this.socket.on('close', function() { + self.destroy(); + }); +} +util.inherits(Client, Transform); + +Client.prototype.destroy = function destroy(msg) { + this.socket.destroy(); + + this.emit('close'); +}; + +Client.prototype._transform = function _transform(data, enc, cb) { + cb(); + + this.buffer += data; + + while (true) { + if (this.state === 'headers') { + // Not enough data + if (!/\r\n/.test(this.buffer)) + break; + + if (/^\r\n/.test(this.buffer)) { + this.buffer = this.buffer.slice(2); + this.state = 'body'; + continue; + } + + // Match: + // Header-name: header-value\r\n + var match = this.buffer.match(/^([^:\s\r\n]+)\s*:\s*([^\s\r\n]+)\r\n/); + if (!match) + return this.destroy('Expected header, but failed to parse it'); + + this.headers[match[1].toLowerCase()] = match[2]; + + this.buffer = this.buffer.slice(match[0].length); + } else { + var len = this.headers['content-length']; + if (len === undefined) + return this.destroy('Expected content-length'); + + len = len | 0; + if (Buffer.byteLength(this.buffer) < len) + break; + + this.push(new Command(this.headers, this.buffer.slice(0, len))); + this.state = 'headers'; + this.buffer = this.buffer.slice(len); + this.headers = {}; + } + } +}; + +Client.prototype.send = function send(headers, data) { + if (!data) + data = ''; + + var out = []; + Object.keys(headers).forEach(function(key) { + out.push(key + ': ' + headers[key]); + }); + out.push('Content-Length: ' + Buffer.byteLength(data), ''); + + this.socket.cork(); + this.socket.write(out.join('\r\n') + '\r\n'); + + if (data.length > 0) + this.socket.write(data); + this.socket.uncork(); +}; + +Client.prototype.start = function start() { + this.send({ + Type: 'connect', + 'V8-Version': process.versions.v8, + 'Protocol-Version': 1, + 'Embedding-Host': 'node ' + process.version + }); +}; + +Client.prototype.onCommand = function onCommand(cmd) { + this.binding.sendCommand(cmd.body); + + this.agent.notifyWait(); +}; + +function Command(headers, body) { + this.headers = headers; + this.body = body; +} diff --git a/deps/debugger-agent/src/agent.cc b/deps/debugger-agent/src/agent.cc new file mode 100644 index 0000000000..335737ffe9 --- /dev/null +++ b/deps/debugger-agent/src/agent.cc @@ -0,0 +1,347 @@ +// Copyright Fedor Indutny and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "agent.h" +#include "debugger-agent.h" + +#include "node.h" +#include "node_internals.h" // ARRAY_SIZE +#include "env.h" +#include "env-inl.h" +#include "v8.h" +#include "v8-debug.h" +#include "util.h" +#include "util-inl.h" +#include "queue.h" + +#include + +namespace node { +namespace debugger { + +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Handle; +using v8::HandleScope; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Locker; +using v8::Object; +using v8::String; +using v8::Value; + + +Agent::Agent(Environment* env) : state_(kNone), + port_(5858), + wait_(false), + parent_env_(env), + child_env_(NULL), + dispatch_handler_(NULL) { + int err; + + err = uv_sem_init(&start_sem_, 0); + CHECK_EQ(err, 0); + + err = uv_mutex_init(&message_mutex_); + CHECK_EQ(err, 0); + + QUEUE_INIT(&messages_); +} + + +Agent::~Agent() { + Stop(); + + uv_sem_destroy(&start_sem_); + uv_mutex_destroy(&message_mutex_); + + // Clean-up messages + while (!QUEUE_EMPTY(&messages_)) { + QUEUE* q = QUEUE_HEAD(&messages_); + QUEUE_REMOVE(q); + AgentMessage* msg = ContainerOf(&AgentMessage::member, q); + delete msg; + } +} + + +bool Agent::Start(int port, bool wait) { + int err; + + if (state_ == kRunning) + return false; + + err = uv_loop_init(&child_loop_); + if (err != 0) + goto loop_init_failed; + + // Interruption signal handler + err = uv_async_init(&child_loop_, &child_signal_, ChildSignalCb); + if (err != 0) + goto async_init_failed; + uv_unref(reinterpret_cast(&child_signal_)); + + port_ = port; + wait_ = wait; + + err = uv_thread_create(&thread_, + reinterpret_cast(ThreadCb), + this); + if (err != 0) + goto thread_create_failed; + + uv_sem_wait(&start_sem_); + + state_ = kRunning; + + return true; + + thread_create_failed: + uv_close(reinterpret_cast(&child_signal_), NULL); + + async_init_failed: + err = uv_loop_close(&child_loop_); + CHECK_EQ(err, 0); + + loop_init_failed: + return false; +} + + +void Agent::Enable() { + v8::Debug::SetMessageHandler(MessageHandler); + + // Assign environment to the debugger's context + // NOTE: The debugger context is created after `SetMessageHandler()` call + parent_env()->AssignToContext(v8::Debug::GetDebugContext()); +} + + +void Agent::Stop() { + int err; + + if (state_ != kRunning) { + return; + } + + v8::Debug::SetMessageHandler(NULL); + + // Send empty message to terminate things + EnqueueMessage(new AgentMessage(NULL, 0)); + + // Signal worker thread to make it stop + err = uv_async_send(&child_signal_); + CHECK_EQ(err, 0); + + err = uv_thread_join(&thread_); + CHECK_EQ(err, 0); + + uv_close(reinterpret_cast(&child_signal_), NULL); + uv_run(&child_loop_, UV_RUN_NOWAIT); + + err = uv_loop_close(&child_loop_); + CHECK_EQ(err, 0); + + state_ = kNone; +} + + +void Agent::WorkerRun() { + static const char* argv[] = { "node", "--debug-agent" }; + Isolate* isolate = Isolate::New(); + { + Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + + HandleScope handle_scope(isolate); + Local context = Context::New(isolate); + + Context::Scope context_scope(context); + Environment* env = CreateEnvironment( + isolate, + &child_loop_, + context, + ARRAY_SIZE(argv), + argv, + ARRAY_SIZE(argv), + argv); + + child_env_ = env; + + // Expose API + InitAdaptor(env); + LoadEnvironment(env); + + CHECK_EQ(&child_loop_, env->event_loop()); + uv_run(&child_loop_, UV_RUN_DEFAULT); + + // Clean-up peristent + api_.Reset(); + + // Clean-up all running handles + env->CleanupHandles(); + + env->Dispose(); + env = NULL; + } + isolate->Dispose(); +} + + +void Agent::InitAdaptor(Environment* env) { + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + + // Create API adaptor + Local t = FunctionTemplate::New(isolate); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(String::NewFromUtf8(isolate, "DebugAPI")); + + NODE_SET_PROTOTYPE_METHOD(t, "notifyListen", NotifyListen); + NODE_SET_PROTOTYPE_METHOD(t, "notifyWait", NotifyWait); + NODE_SET_PROTOTYPE_METHOD(t, "sendCommand", SendCommand); + + Local api = t->GetFunction()->NewInstance(); + api->SetAlignedPointerInInternalField(0, this); + + api->Set(String::NewFromUtf8(isolate, "port"), Integer::New(isolate, port_)); + + env->process_object()->Set(String::NewFromUtf8(isolate, "_debugAPI"), api); + api_.Reset(env->isolate(), api); +} + + +Agent* Agent::Unwrap(const v8::FunctionCallbackInfo& args) { + void* ptr = args.Holder()->GetAlignedPointerFromInternalField(0); + return reinterpret_cast(ptr); +} + + +void Agent::NotifyListen(const FunctionCallbackInfo& args) { + Agent* a = Unwrap(args); + + // Notify other thread that we are ready to process events + uv_sem_post(&a->start_sem_); +} + + +void Agent::NotifyWait(const FunctionCallbackInfo& args) { + Agent* a = Unwrap(args); + + a->wait_ = false; + + int err = uv_async_send(&a->child_signal_); + CHECK_EQ(err, 0); +} + + +void Agent::SendCommand(const FunctionCallbackInfo& args) { + Agent* a = Unwrap(args); + Environment* env = a->child_env(); + HandleScope scope(env->isolate()); + + String::Value v(args[0]); + + v8::Debug::SendCommand(a->parent_env()->isolate(), *v, v.length()); + if (a->dispatch_handler_ != NULL) + a->dispatch_handler_(a->parent_env()); +} + + +void Agent::ThreadCb(Agent* agent) { + agent->WorkerRun(); +} + + +void Agent::ChildSignalCb(uv_async_t* signal) { + Agent* a = ContainerOf(&Agent::child_signal_, signal); + Isolate* isolate = a->child_env()->isolate(); + + HandleScope scope(isolate); + Local api = PersistentToLocal(isolate, a->api_); + + uv_mutex_lock(&a->message_mutex_); + while (!QUEUE_EMPTY(&a->messages_)) { + QUEUE* q = QUEUE_HEAD(&a->messages_); + AgentMessage* msg = ContainerOf(&AgentMessage::member, q); + + // Time to close everything + if (msg->data() == NULL) { + QUEUE_REMOVE(q); + delete msg; + + MakeCallback(isolate, api, "onclose", 0, NULL); + break; + } + + // Waiting for client, do not send anything just yet + // TODO(indutny): move this to js-land + if (a->wait_) + break; + + QUEUE_REMOVE(q); + Local argv[] = { + String::NewFromTwoByte(isolate, + msg->data(), + String::kNormalString, + msg->length()) + }; + + // Emit message + MakeCallback(isolate, + api, + "onmessage", + ARRAY_SIZE(argv), + argv); + delete msg; + } + uv_mutex_unlock(&a->message_mutex_); +} + + +void Agent::EnqueueMessage(AgentMessage* message) { + uv_mutex_lock(&message_mutex_); + QUEUE_INSERT_TAIL(&messages_, &message->member); + uv_mutex_unlock(&message_mutex_); + uv_async_send(&child_signal_); +} + + +void Agent::MessageHandler(const v8::Debug::Message& message) { + Isolate* isolate = message.GetIsolate(); + Environment* env = Environment::GetCurrent(isolate); + Agent* a = env->debugger_agent(); + CHECK_NE(a, NULL); + CHECK_EQ(isolate, a->parent_env()->isolate()); + + HandleScope scope(isolate); + Local json = message.GetJSON(); + String::Value v(json); + + AgentMessage* msg = new AgentMessage(*v, v.length()); + a->EnqueueMessage(msg); +} + +} // namespace debugger +} // namespace node diff --git a/deps/debugger-agent/src/agent.h b/deps/debugger-agent/src/agent.h new file mode 100644 index 0000000000..82db5e5e18 --- /dev/null +++ b/deps/debugger-agent/src/agent.h @@ -0,0 +1,64 @@ +// Copyright Fedor Indutny and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef DEPS_DEBUGGER_AGENT_SRC_AGENT_H_ +#define DEPS_DEBUGGER_AGENT_SRC_AGENT_H_ + +#include "v8.h" +#include "v8-debug.h" +#include "queue.h" + +#include +#include + +namespace node { +namespace debugger { + +class AgentMessage { + public: + AgentMessage(uint16_t* val, int length) : length_(length) { + if (val == NULL) { + data_ = val; + } else { + data_ = new uint16_t[length]; + memcpy(data_, val, length * sizeof(*data_)); + } + } + + ~AgentMessage() { + delete[] data_; + data_ = NULL; + } + + inline const uint16_t* data() const { return data_; } + inline int length() const { return length_; } + + QUEUE member; + + private: + uint16_t* data_; + int length_; +}; + +} // namespace debugger +} // namespace node + +#endif // DEPS_DEBUGGER_AGENT_SRC_AGENT_H_ diff --git a/lib/_debugger.js b/lib/_debugger.js index 4b01d39e5b..41d3fc4210 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -249,6 +249,10 @@ Client.prototype._onResponse = function(res) { this._removeScript(res.body.body.script); handled = true; + } else if (res.body && res.body.event === 'compileError') { + // This event is not used anywhere right now, perhaps somewhere in the + // future? + handled = true; } if (cb) { diff --git a/node.gyp b/node.gyp index de48ae8378..daf06c7a7a 100644 --- a/node.gyp +++ b/node.gyp @@ -67,6 +67,7 @@ 'lib/util.js', 'lib/vm.js', 'lib/zlib.js', + 'deps/debugger-agent/lib/_debugger_agent.js', ], }, @@ -77,6 +78,7 @@ 'dependencies': [ 'node_js2c#host', + 'deps/debugger-agent/debugger-agent.gyp:debugger-agent', ], 'include_dirs': [ diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index f3911df5ce..356df86907 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1200,6 +1200,20 @@ static void StrError(const FunctionCallbackInfo& args) { } +static void CaresTimerCloseCb(uv_handle_t* handle) { + Environment* env = Environment::from_cares_timer_handle( + reinterpret_cast(handle)); + env->FinishHandleCleanup(handle); +} + + +static void CaresTimerClose(Environment* env, + uv_handle_t* handle, + void* arg) { + uv_close(handle, CaresTimerCloseCb); +} + + static void Initialize(Handle target, Handle unused, Handle context) { @@ -1223,6 +1237,10 @@ static void Initialize(Handle target, /* Initialize the timeout timer. The timer won't be started until the */ /* first socket is opened. */ uv_timer_init(env->event_loop(), env->cares_timer_handle()); + env->RegisterHandleCleanup( + reinterpret_cast(env->cares_timer_handle()), + CaresTimerClose, + NULL); NODE_SET_METHOD(target, "queryA", Query); NODE_SET_METHOD(target, "queryAaaa", Query); diff --git a/src/env-inl.h b/src/env-inl.h index 4bc5eb0986..e6a3d1c5ec 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -74,10 +74,10 @@ inline Environment::IsolateData* Environment::IsolateData::Get( } inline Environment::IsolateData* Environment::IsolateData::GetOrCreate( - v8::Isolate* isolate) { + v8::Isolate* isolate, uv_loop_t* loop) { IsolateData* isolate_data = Get(isolate); if (isolate_data == NULL) { - isolate_data = new IsolateData(isolate); + isolate_data = new IsolateData(isolate, loop); isolate->SetData(kIsolateSlot, isolate_data); } isolate_data->ref_count_ += 1; @@ -91,8 +91,9 @@ inline void Environment::IsolateData::Put() { } } -inline Environment::IsolateData::IsolateData(v8::Isolate* isolate) - : event_loop_(uv_default_loop()), +inline Environment::IsolateData::IsolateData(v8::Isolate* isolate, + uv_loop_t* loop) + : event_loop_(loop), isolate_(isolate), #define V(PropertyName, StringValue) \ PropertyName ## _(isolate, FIXED_ONE_BYTE_STRING(isolate, StringValue)), @@ -188,8 +189,9 @@ inline void Environment::TickInfo::set_last_threw(bool value) { last_threw_ = value; } -inline Environment* Environment::New(v8::Local context) { - Environment* env = new Environment(context); +inline Environment* Environment::New(v8::Local context, + uv_loop_t* loop) { + Environment* env = new Environment(context, loop); env->AssignToContext(context); return env; } @@ -207,12 +209,14 @@ inline Environment* Environment::GetCurrent(v8::Local context) { context->GetAlignedPointerFromEmbedderData(kContextEmbedderDataIndex)); } -inline Environment::Environment(v8::Local context) +inline Environment::Environment(v8::Local context, + uv_loop_t* loop) : isolate_(context->GetIsolate()), - isolate_data_(IsolateData::GetOrCreate(context->GetIsolate())), + isolate_data_(IsolateData::GetOrCreate(context->GetIsolate(), loop)), using_smalloc_alloc_cb_(false), using_domains_(false), printed_error_(false), + debugger_agent_(this), context_(context->GetIsolate(), context) { // We'll be creating new objects so make sure we've entered the context. v8::HandleScope handle_scope(isolate()); @@ -221,6 +225,10 @@ inline Environment::Environment(v8::Local context) set_module_load_list_array(v8::Array::New(isolate())); RB_INIT(&cares_task_list_); QUEUE_INIT(&gc_tracker_queue_); + QUEUE_INIT(&req_wrap_queue_); + QUEUE_INIT(&handle_wrap_queue_); + QUEUE_INIT(&handle_cleanup_queue_); + handle_cleanup_waiting_ = 0; } inline Environment::~Environment() { @@ -233,6 +241,21 @@ inline Environment::~Environment() { isolate_data()->Put(); } +inline void Environment::CleanupHandles() { + while (!QUEUE_EMPTY(&handle_cleanup_queue_)) { + QUEUE* q = QUEUE_HEAD(&handle_cleanup_queue_); + QUEUE_REMOVE(q); + + HandleCleanup* hc = ContainerOf(&HandleCleanup::handle_cleanup_queue_, q); + handle_cleanup_waiting_++; + hc->cb_(this, hc->handle_, hc->arg_); + delete hc; + } + + while (handle_cleanup_waiting_ != 0) + uv_run(event_loop(), UV_RUN_ONCE); +} + inline void Environment::Dispose() { delete this; } @@ -287,6 +310,17 @@ inline uv_check_t* Environment::idle_check_handle() { return &idle_check_handle_; } +inline void Environment::RegisterHandleCleanup(uv_handle_t* handle, + HandleCleanupCb cb, + void *arg) { + HandleCleanup* hc = new HandleCleanup(handle, cb, arg); + QUEUE_INSERT_TAIL(&handle_cleanup_queue_, &hc->handle_cleanup_queue_); +} + +inline void Environment::FinishHandleCleanup(uv_handle_t* handle) { + handle_cleanup_waiting_--; +} + inline uv_loop_t* Environment::event_loop() const { return isolate_data()->event_loop(); } diff --git a/src/env.h b/src/env.h index 015dcd26e8..655e804cb9 100644 --- a/src/env.h +++ b/src/env.h @@ -28,6 +28,7 @@ #include "uv.h" #include "v8.h" #include "queue.h" +#include "debugger-agent.h" #include @@ -356,11 +357,34 @@ 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) { + QUEUE_INIT(&handle_cleanup_queue_); + } + + uv_handle_t* handle_; + HandleCleanupCb cb_; + void* arg_; + QUEUE handle_cleanup_queue_; + }; + static inline Environment* GetCurrent(v8::Isolate* isolate); static inline Environment* GetCurrent(v8::Local context); // See CreateEnvironment() in src/node.cc. - static inline Environment* New(v8::Local context); + static inline Environment* New(v8::Local context, + uv_loop_t* loop); + inline void CleanupHandles(); inline void Dispose(); // Defined in src/node_profiler.cc. @@ -385,6 +409,12 @@ class Environment { static inline Environment* from_idle_check_handle(uv_check_t* handle); 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 void FinishHandleCleanup(uv_handle_t* handle); + inline AsyncListener* async_listener(); inline DomainFlag* domain_flag(); inline TickInfo* tick_info(); @@ -434,12 +464,19 @@ class Environment { ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) #undef V + inline debugger::Agent* debugger_agent() { + return &debugger_agent_; + } + + inline QUEUE* handle_wrap_queue() { return &handle_wrap_queue_; } + inline QUEUE* req_wrap_queue() { return &req_wrap_queue_; } + private: static const int kIsolateSlot = NODE_ISOLATE_SLOT; class GCInfo; class IsolateData; - inline explicit Environment(v8::Local context); + inline Environment(v8::Local context, uv_loop_t* loop); inline ~Environment(); inline IsolateData* isolate_data() const; void AfterGarbageCollectionCallback(const GCInfo* before, @@ -465,6 +502,12 @@ class Environment { bool using_domains_; QUEUE gc_tracker_queue_; bool printed_error_; + debugger::Agent debugger_agent_; + + QUEUE handle_wrap_queue_; + QUEUE req_wrap_queue_; + QUEUE handle_cleanup_queue_; + int handle_cleanup_waiting_; #define V(PropertyName, TypeName) \ v8::Persistent PropertyName ## _; @@ -494,7 +537,8 @@ class Environment { // Per-thread, reference-counted singleton. class IsolateData { public: - static inline IsolateData* GetOrCreate(v8::Isolate* isolate); + static inline IsolateData* GetOrCreate(v8::Isolate* isolate, + uv_loop_t* loop); inline void Put(); inline uv_loop_t* event_loop() const; @@ -509,7 +553,7 @@ class Environment { private: inline static IsolateData* Get(v8::Isolate* isolate); - inline explicit IsolateData(v8::Isolate* isolate); + inline explicit IsolateData(v8::Isolate* isolate, uv_loop_t* loop); inline v8::Isolate* isolate() const; // Defined in src/node_profiler.cc. diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index f713750d7f..83015f1dfd 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -39,9 +39,6 @@ using v8::Local; using v8::Object; using v8::Value; -// defined in node.cc -extern QUEUE handle_wrap_queue; - void HandleWrap::Ref(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args.GetIsolate()); @@ -100,7 +97,7 @@ HandleWrap::HandleWrap(Environment* env, handle__->data = this; HandleScope scope(env->isolate()); Wrap(object, this); - QUEUE_INSERT_TAIL(&handle_wrap_queue, &handle_wrap_queue_); + QUEUE_INSERT_TAIL(env->handle_wrap_queue(), &handle_wrap_queue_); } diff --git a/src/node.cc b/src/node.cc index 0e798b1353..1736f9fbb5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -122,10 +122,6 @@ using v8::V8; using v8::Value; using v8::kExternalUint32Array; -// FIXME(bnoordhuis) Make these per-context? -QUEUE handle_wrap_queue = { &handle_wrap_queue, &handle_wrap_queue }; -QUEUE req_wrap_queue = { &req_wrap_queue, &req_wrap_queue }; - static bool print_eval = false; static bool force_repl = false; static bool trace_deprecation = false; @@ -1554,13 +1550,14 @@ static Local ExecuteString(Environment* env, static void GetActiveRequests(const FunctionCallbackInfo& args) { - HandleScope scope(args.GetIsolate()); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + HandleScope scope(env->isolate()); Local ary = Array::New(args.GetIsolate()); QUEUE* q = NULL; int i = 0; - QUEUE_FOREACH(q, &req_wrap_queue) { + QUEUE_FOREACH(q, env->req_wrap_queue()) { ReqWrap* w = ContainerOf(&ReqWrap::req_wrap_queue_, q); if (w->persistent().IsEmpty()) continue; @@ -1583,7 +1580,7 @@ void GetActiveHandles(const FunctionCallbackInfo& args) { Local owner_sym = env->owner_string(); - QUEUE_FOREACH(q, &handle_wrap_queue) { + QUEUE_FOREACH(q, env->handle_wrap_queue()) { HandleWrap* w = ContainerOf(&HandleWrap::handle_wrap_queue_, q); if (w->persistent().IsEmpty() || (w->flags_ & HandleWrap::kUnref)) continue; @@ -1967,8 +1964,8 @@ static void Uptime(const FunctionCallbackInfo& args) { HandleScope scope(env->isolate()); double uptime; - uv_update_time(uv_default_loop()); - uptime = uv_now(uv_default_loop()) - prog_start_time; + uv_update_time(env->event_loop()); + uptime = uv_now(env->event_loop()) - prog_start_time; args.GetReturnValue().Set(Number::New(env->isolate(), uptime / 1000)); } @@ -2860,9 +2857,12 @@ static void RawDebug(const FunctionCallbackInfo& args) { } -void Load(Environment* env) { +void LoadEnvironment(Environment* env) { HandleScope handle_scope(env->isolate()); + V8::SetFatalErrorHandler(node::OnFatalError); + V8::AddMessageListener(OnMessage); + // Compile, execute the src/node.js file. (Which was included as static C // string in node_natives.h. 'natve_node' is the string containing that // source code.) @@ -3121,40 +3121,33 @@ static void ParseArgs(int* argc, // Called from V8 Debug Agent TCP thread. -static void DispatchMessagesDebugAgentCallback() { +static void DispatchMessagesDebugAgentCallback(Environment* env) { + // TODO(indutny): move async handle to environment uv_async_send(&dispatch_debug_messages_async); } -// Called from the main thread. -static void EnableDebug(Isolate* isolate, bool wait_connect) { - assert(debugger_running == false); - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); - v8::Debug::SetDebugMessageDispatchHandler(DispatchMessagesDebugAgentCallback, - false); - debugger_running = v8::Debug::EnableAgent("node " NODE_VERSION, - debug_port, - wait_connect); +static void StartDebug(Environment* env, bool wait) { + CHECK(!debugger_running); + + env->debugger_agent()->set_dispatch_handler( + DispatchMessagesDebugAgentCallback); + debugger_running = env->debugger_agent()->Start(debug_port, wait); if (debugger_running == false) { fprintf(stderr, "Starting debugger on port %d failed\n", debug_port); fflush(stderr); return; } - fprintf(stderr, "Debugger listening on port %d\n", debug_port); - fflush(stderr); +} - if (isolate == NULL) - return; // Still starting up. - Local context = isolate->GetCurrentContext(); - if (context.IsEmpty()) - return; // Still starting up. - Environment* env = Environment::GetCurrent(context); - // Assign environment to the debugger's context - env->AssignToContext(v8::Debug::GetDebugContext()); +// Called from the main thread. +static void EnableDebug(Environment* env) { + CHECK(debugger_running); + + // Send message to enable debug in workers + HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); Local message = Object::New(env->isolate()); message->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "cmd"), FIXED_ONE_BYTE_STRING(env->isolate(), "NODE_DEBUG_ENABLED")); @@ -3163,6 +3156,9 @@ static void EnableDebug(Isolate* isolate, bool wait_connect) { message }; MakeCallback(env, env->process_object(), "emit", ARRAY_SIZE(argv), argv); + + // Enabled debugger, possibly making it wait on a semaphore + env->debugger_agent()->Enable(); } @@ -3170,7 +3166,12 @@ static void EnableDebug(Isolate* isolate, bool wait_connect) { static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) { if (debugger_running == false) { fprintf(stderr, "Starting debugger agent.\n"); - EnableDebug(node_isolate, false); + + Environment* env = Environment::GetCurrent(node_isolate); + Context::Scope context_scope(env->context()); + + StartDebug(env, false); + EnableDebug(env); } Isolate::Scope isolate_scope(node_isolate); v8::Debug::ProcessDebugMessages(); @@ -3399,7 +3400,8 @@ static void DebugPause(const FunctionCallbackInfo& args) { static void DebugEnd(const FunctionCallbackInfo& args) { if (debugger_running) { - v8::Debug::DisableAgent(); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + env->debugger_agent()->Stop(); debugger_running = false; } } @@ -3512,13 +3514,7 @@ void Init(int* argc, RegisterSignalHandler(SIGTERM, SignalExit, true); #endif // __POSIX__ - V8::SetFatalErrorHandler(node::OnFatalError); - V8::AddMessageListener(OnMessage); - - // If the --debug flag was specified then initialize the debug thread. - if (use_debug_agent) { - EnableDebug(node_isolate, debug_wait_connect); - } else { + if (!use_debug_agent) { RegisterDebugSignalHandler(); } } @@ -3591,22 +3587,62 @@ int EmitExit(Environment* env) { } +// Just a convenience method Environment* CreateEnvironment(Isolate* isolate, Handle context, int argc, const char* const* argv, int exec_argc, const char* const* exec_argv) { + Environment* env; + Context::Scope context_scope(context); + + env = CreateEnvironment(isolate, + uv_default_loop(), + context, + argc, + argv, + exec_argc, + exec_argv); + + LoadEnvironment(env); + + return env; +} + + +static void HandleCloseCb(uv_handle_t* handle) { + Environment* env = reinterpret_cast(handle->data); + env->FinishHandleCleanup(handle); +} + + +static void HandleCleanup(Environment* env, + uv_handle_t* handle, + void* arg) { + handle->data = env; + uv_close(handle, HandleCloseCb); +} + + +Environment* CreateEnvironment(Isolate* isolate, + uv_loop_t* loop, + Handle context, + int argc, + const char* const* argv, + int exec_argc, + const char* const* exec_argv) { HandleScope handle_scope(isolate); Context::Scope context_scope(context); - Environment* env = Environment::New(context); + Environment* env = Environment::New(context, loop); isolate->SetAutorunMicrotasks(false); uv_check_init(env->event_loop(), env->immediate_check_handle()); uv_unref( reinterpret_cast(env->immediate_check_handle())); + uv_idle_init(env->event_loop(), env->immediate_idle_handle()); // Inform V8's CPU profiler when we're idle. The profiler is sampling-based @@ -3623,6 +3659,24 @@ Environment* CreateEnvironment(Isolate* isolate, uv_unref(reinterpret_cast(env->idle_prepare_handle())); uv_unref(reinterpret_cast(env->idle_check_handle())); + // Register handle cleanups + env->RegisterHandleCleanup( + reinterpret_cast(env->immediate_check_handle()), + HandleCleanup, + NULL); + env->RegisterHandleCleanup( + reinterpret_cast(env->immediate_idle_handle()), + HandleCleanup, + NULL); + env->RegisterHandleCleanup( + reinterpret_cast(env->idle_prepare_handle()), + HandleCleanup, + NULL); + env->RegisterHandleCleanup( + reinterpret_cast(env->idle_check_handle()), + HandleCleanup, + NULL); + if (v8_is_profiling) { StartProfilerIdleNotifier(env); } @@ -3634,7 +3688,6 @@ Environment* CreateEnvironment(Isolate* isolate, env->set_process_object(process_object); SetupProcessObject(env, argc, argv, exec_argc, exec_argv); - Load(env); return env; } @@ -3676,34 +3729,41 @@ int Start(int argc, char** argv) { HandleScope handle_scope(node_isolate); Local context = Context::New(node_isolate); Environment* env = CreateEnvironment( - node_isolate, context, argc, argv, exec_argc, exec_argv); - // Assign env to the debugger's context - if (debugger_running) { - HandleScope scope(env->isolate()); - env->AssignToContext(v8::Debug::GetDebugContext()); - } - // This Context::Scope is here so EnableDebug() can look up the current - // environment with Environment::GetCurrent(). - // TODO(bnoordhuis) Reorder the debugger initialization logic so it can - // be removed. - { - Context::Scope context_scope(env->context()); - bool more; - do { - more = uv_run(env->event_loop(), UV_RUN_ONCE); - if (more == false) { - EmitBeforeExit(env); - - // Emit `beforeExit` if the loop became alive either after emitting - // event, or after running some callbacks. - more = uv_loop_alive(env->event_loop()); - if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0) - more = true; - } - } while (more == true); - code = EmitExit(env); - RunAtExit(env); - } + node_isolate, + uv_default_loop(), + context, + argc, + argv, + exec_argc, + exec_argv); + Context::Scope context_scope(context); + + // Start debug agent when argv has --debug + if (use_debug_agent) + StartDebug(env, debug_wait_connect); + + LoadEnvironment(env); + + // Enable debugger + if (use_debug_agent) + EnableDebug(env); + + bool more; + do { + more = uv_run(env->event_loop(), UV_RUN_ONCE); + if (more == false) { + EmitBeforeExit(env); + + // Emit `beforeExit` if the loop became alive either after emitting + // event, or after running some callbacks. + more = uv_loop_alive(env->event_loop()); + if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0) + more = true; + } + } while (more == true); + code = EmitExit(env); + RunAtExit(env); + env->Dispose(); env = NULL; } diff --git a/src/node.h b/src/node.h index 4eaaa07859..ca7cec0c45 100644 --- a/src/node.h +++ b/src/node.h @@ -63,6 +63,9 @@ #define NODE_DEPRECATED(msg, fn) V8_DEPRECATED(msg, fn) +// Forward-declare libuv loop +struct uv_loop_s; + // Forward-declare these functions now to stop MSVS from becoming // terminally confused when it's done in node_internals.h namespace node { @@ -177,12 +180,26 @@ NODE_EXTERN void Init(int* argc, class Environment; +NODE_EXTERN Environment* CreateEnvironment(v8::Isolate* isolate, + struct uv_loop_s* loop, + v8::Handle context, + int argc, + const char* const* argv, + int exec_argc, + const char* const* exec_argv); +NODE_EXTERN void LoadEnvironment(Environment* env); + +// NOTE: Calling this is the same as calling +// CreateEnvironment() + LoadEnvironment() from above. +// `uv_default_loop()` will be passed as `loop`. NODE_EXTERN Environment* CreateEnvironment(v8::Isolate* isolate, v8::Handle context, int argc, const char* const* argv, int exec_argc, const char* const* exec_argv); + + NODE_EXTERN void EmitBeforeExit(Environment* env); NODE_EXTERN int EmitExit(Environment* env); NODE_EXTERN void RunAtExit(Environment* env); diff --git a/src/node.js b/src/node.js index e7b45ad5cc..19d9506446 100644 --- a/src/node.js +++ b/src/node.js @@ -56,7 +56,10 @@ startup.processKillAndExit(); startup.processSignalHandlers(); - startup.processChannel(); + // 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') + startup.processChannel(); startup.processRawDebug(); @@ -80,6 +83,11 @@ var d = NativeModule.require('_debugger'); d.start(); + } else if (process.argv[1] == '--debug-agent') { + // Start the debugger agent + var d = NativeModule.require('_debugger_agent'); + d.start(); + } else if (process._eval != null) { // User passed '-e' or '--eval' arguments to Node. evalScript('[eval]'); diff --git a/src/req_wrap.h b/src/req_wrap.h index 56d63eb711..df4ed2ced1 100644 --- a/src/req_wrap.h +++ b/src/req_wrap.h @@ -31,9 +31,6 @@ namespace node { -// defined in node.cc -extern QUEUE req_wrap_queue; - template class ReqWrap : public AsyncWrap { public: @@ -44,7 +41,7 @@ class ReqWrap : public AsyncWrap { if (env->in_domain()) object->Set(env->domain_string(), env->domain_array()->Get(0)); - QUEUE_INSERT_TAIL(&req_wrap_queue, &req_wrap_queue_); + QUEUE_INSERT_TAIL(env->req_wrap_queue(), &req_wrap_queue_); } diff --git a/test/simple/test-debug-brk-no-arg.js b/test/disabled/test-debug-brk-no-arg.js similarity index 100% rename from test/simple/test-debug-brk-no-arg.js rename to test/disabled/test-debug-brk-no-arg.js