Skip to content

Commit

Permalink
deps: re-implement debugger-agent
Browse files Browse the repository at this point in the history
Reviewed-By: Trevor Norris <trevnorris@gmail.com>
PR-URL: nodejs/node-v0.x-archive#8476
  • Loading branch information
indutny committed Oct 8, 2014
1 parent 8efcc7f commit 7a0cfe9
Show file tree
Hide file tree
Showing 17 changed files with 1,009 additions and 93 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions deps/debugger-agent/debugger-agent.gyp
Original file line number Diff line number Diff line change
@@ -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",
],
}],
}
109 changes: 109 additions & 0 deletions deps/debugger-agent/include/debugger-agent.h
Original file line number Diff line number Diff line change
@@ -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<v8::Value>& args);
static void NotifyListen(const v8::FunctionCallbackInfo<v8::Value>& args);
static void NotifyWait(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SendCommand(const v8::FunctionCallbackInfo<v8::Value>& 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<v8::Object> api_;

// QUEUE
void* messages_[2];

DispatchHandler dispatch_handler_;
};

} // namespace debugger
} // namespace node

#endif // DEPS_DEBUGGER_AGENT_INCLUDE_DEBUGGER_AGENT_H_
191 changes: 191 additions & 0 deletions deps/debugger-agent/lib/_debugger_agent.js
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 7a0cfe9

Please sign in to comment.