// Copyright Joyent, Inc. 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define isolate_debugger_constructor NODE_VAR(isolate_debugger_constructor)
#define ISOLATEMESSAGE_SHARED_STREAM 0x0001
namespace node {
using v8::Arguments;
using v8::Array;
using v8::Context;
using v8::False;
using v8::Function;
using v8::FunctionTemplate;
using v8::Handle;
using v8::HandleScope;
using v8::Integer;
using v8::Local;
using v8::Null;
using v8::Object;
using v8::ObjectTemplate;
using v8::Persistent;
using v8::String;
using v8::True;
using v8::Undefined;
using v8::Value;
using v8::Undefined;
static volatile bool initialized;
static volatile int id;
static volatile int isolate_count;
static ngx_queue_t isolate_list;
static uv_mutex_t isolate_mutex;
#ifdef NDEBUG
# define IF_DEBUG(expr)
#else
# define IF_DEBUG(expr) expr;
#endif
template
class Queue {
public:
Queue() {
if (uv_mutex_init(&mutex_)) abort();
ngx_queue_init(&queue_);
}
~Queue() {
IF_DEBUG({
uv_mutex_lock(&mutex_);
assert(ngx_queue_empty(&queue_));
uv_mutex_unlock(&mutex_);
})
uv_mutex_destroy(&mutex_);
}
void Produce(T item) {
Message* m = new Message;
m->item_ = item;
uv_mutex_lock(&mutex_);
ngx_queue_insert_tail(&queue_, &m->queue_);
uv_mutex_unlock(&mutex_);
}
bool Consume(T& item) {
ngx_queue_t* q = NULL;
uv_mutex_lock(&mutex_);
if (!ngx_queue_empty(&queue_)) {
q = ngx_queue_head(&queue_);
ngx_queue_remove(q);
}
uv_mutex_unlock(&mutex_);
if (q == NULL) return false;
Message* m = ngx_queue_data(q, Message, queue_);
item = m->item_;
delete m;
return true;
}
private:
struct Message {
ngx_queue_t queue_;
T item_;
};
ngx_queue_t queue_;
uv_mutex_t mutex_;
};
template
class Channel {
public:
typedef void (*Callback)(T item, void* arg);
Channel(uv_loop_t* loop, Callback callback, void* arg) {
callback_ = callback;
arg_ = arg;
uv_async_init(loop, &async_, OnMessage);
uv_unref(loop);
}
~Channel() {
uv_ref(async_.loop);
uv_close(reinterpret_cast(&async_), NULL);
}
void Send(T item) {
queue_.Produce(item);
uv_async_send(&async_);
}
private:
static void OnMessage(uv_async_t* handle, int status) {
Channel* c = container_of(handle, Channel, async_);
c->OnMessage();
}
void OnMessage() {
T item;
while (queue_.Consume(item)) callback_(item, arg_);
}
void* arg_;
Callback callback_;
uv_async_t async_;
Queue queue_;
};
struct IsolateMessage {
int flags;
struct {
size_t size_;
char* buffer_;
} data_;
uv_stream_info_t shared_stream_info_;
IsolateMessage(const char* buffer, size_t size,
uv_stream_info_t* shared_stream_info) {
flags = 0;
// make a copy for now
data_.size_ = size;
data_.buffer_ = new char[size];
memcpy(data_.buffer_, buffer, size);
if (shared_stream_info) {
flags |= ISOLATEMESSAGE_SHARED_STREAM;
shared_stream_info_ = *shared_stream_info;
}
}
~IsolateMessage() {
delete[] data_.buffer_;
}
static void Free(char* data, void* arg) {
IsolateMessage* msg = static_cast(arg);
assert(data == msg->data_.buffer_);
delete msg;
}
};
class IsolateChannel: public Channel {
public:
IsolateChannel(uv_loop_t* loop, Callback callback, void* arg)
: Channel(loop, callback, arg)
{
}
};
Handle Isolate::Send(const Arguments& args) {
HandleScope scope;
Isolate* isolate = Isolate::GetCurrent();
assert(Buffer::HasInstance(args[0]));
assert(isolate->send_channel_ != NULL);
Local obj = args[0]->ToObject();
const char* data = Buffer::Data(obj);
size_t size = Buffer::Length(obj);
IsolateMessage* msg;
if (args[1]->IsObject()) {
uv_stream_info_t stream_info;
Local send_stream_obj = args[1]->ToObject();
assert(send_stream_obj->InternalFieldCount() > 0);
StreamWrap* send_stream_wrap = static_cast(
send_stream_obj->GetPointerFromInternalField(0));
uv_stream_t* send_stream = send_stream_wrap->GetStream();
int r = uv_export(send_stream, &stream_info);
assert(r == 0);
msg = new IsolateMessage(data, size, &stream_info);
} else {
msg = new IsolateMessage(data, size, NULL);
}
isolate->send_channel_->Send(msg);
return Undefined();
}
Handle Isolate::Unref(const Arguments& args) {
HandleScope scope;
Isolate* isolate = Isolate::GetCurrent();
uv_unref(isolate->loop_);
return Undefined();
}
void Isolate::OnMessage(IsolateMessage* msg, void* arg) {
HandleScope scope;
Isolate* self = static_cast(arg);
NODE_ISOLATE_CHECK(self);
Buffer* buf = Buffer::New(msg->data_.buffer_, msg->data_.size_,
IsolateMessage::Free, msg);
int argc = 1;
Handle argv[2] = {
buf->handle_
};
if (msg->flags & ISOLATEMESSAGE_SHARED_STREAM) {
// Instantiate the client javascript object and handle.
Local pending_obj = TCPWrap::Instantiate();
// Unwrap the client javascript object.
assert(pending_obj->InternalFieldCount() > 0);
TCPWrap* pending_wrap =
static_cast(pending_obj->GetPointerFromInternalField(0));
int r = uv_import(pending_wrap->GetStream(), &msg->shared_stream_info_);
assert(r == 0);
argv[1] = pending_obj;
argc++;
}
MakeCallback(self->globals_.process, "_onmessage", argc, argv);
}
void Isolate::Initialize() {
if (initialized) return;
if (uv_mutex_init(&isolate_mutex)) abort();
ngx_queue_init(&isolate_list);
initialized = true;
}
int Isolate::Count() {
int count;
uv_mutex_lock(&isolate_mutex);
count = isolate_count;
uv_mutex_unlock(&isolate_mutex);
return count;
}
Isolate::Isolate() {
send_channel_ = NULL; // set (and deleted) by the parent isolate
recv_channel_ = NULL;
uv_mutex_lock(&isolate_mutex);
assert(initialized && "node::Isolate::Initialize() hasn't been called");
ngx_queue_insert_tail(&isolate_list, &isolate_list_);
isolate_count++;
id_ = ++id;
uv_mutex_unlock(&isolate_mutex);
if (id_ == 1) {
loop_ = uv_default_loop();
} else {
loop_ = uv_loop_new();
// Artificially ref the isolate loop so that the child
// isolate stays alive by default. process.exit will
// unref the loop (see Isolate::Unref).
uv_ref(loop_);
}
debug_state = kNone;
debugger_instance = NULL;
ngx_queue_init(&at_exit_callbacks_);
v8_isolate_ = v8::Isolate::New();
assert(v8_isolate_->GetData() == NULL);
v8_isolate_->SetData(this);
globals_init_ = false;
}
Isolate::~Isolate() {
if (!argv_) return;
for (size_t i = 0; argv_[i]; ++i) delete[] argv_[i];
delete[] argv_;
}
struct globals* Isolate::Globals() {
return &globals_;
}
void Isolate::AtExit(AtExitCallback callback, void* arg) {
struct AtExitCallbackInfo* it = new AtExitCallbackInfo;
//NODE_ISOLATE_CHECK(this);
it->callback_ = callback;
it->arg_ = arg;
ngx_queue_insert_head(&at_exit_callbacks_, &it->queue_);
}
void Isolate::Enter() {
v8_isolate_->Enter();
if (v8_context_.IsEmpty()) {
v8_context_ = Context::New();
}
v8_context_->Enter();
if (!globals_init_) {
globals_init_ = true;
globals_init(&globals_);
}
NODE_ISOLATE_CHECK(this);
}
void Isolate::Exit() {
NODE_ISOLATE_CHECK(this);
v8_context_->Exit();
v8_isolate_->Exit();
}
void Isolate::Dispose() {
NODE_ISOLATE_CHECK(this);
while (!ngx_queue_empty(&at_exit_callbacks_)) {
ngx_queue_t* q = ngx_queue_head(&at_exit_callbacks_);
ngx_queue_remove(q);
AtExitCallbackInfo* it = ngx_queue_data(q, AtExitCallbackInfo, queue_);
it->callback_(it->arg_);
delete it;
}
assert(v8_context_->InContext());
v8_context_->Exit();
v8_context_.Clear();
v8_context_.Dispose();
v8_isolate_->Exit();
v8_isolate_->Dispose();
v8_isolate_ = NULL;
uv_mutex_lock(&isolate_mutex);
isolate_count--;
ngx_queue_remove(&isolate_list_);
assert(isolate_count >= 0);
assert((isolate_count == 0 && ngx_queue_empty(&isolate_list))
|| (isolate_count > 0 && !ngx_queue_empty(&isolate_list)));
uv_mutex_unlock(&isolate_mutex);
}
struct IsolateWrap: public ObjectWrap {
public:
IsolateWrap(Isolate* parent_isolate) {
parent_isolate_ = parent_isolate;
uv_loop_t* parent_loop = parent_isolate->GetLoop();
recv_channel_ = new IsolateChannel(
parent_loop, IsolateWrap::OnMessage, this);
isolate_ = new Isolate;
send_channel_ = new IsolateChannel(
isolate_->loop_, Isolate::OnMessage, isolate_);
isolate_->send_channel_ = recv_channel_;
isolate_->recv_channel_ = send_channel_;
// TODO this could be folded into the regular channel
uv_async_init(parent_loop, &child_exit_, AfterChildExit);
isolate_->AtExit(AtChildExit, this);
HandleScope scope;
Local tpl = ObjectTemplate::New();
tpl->SetInternalFieldCount(1);
Local obj = tpl->NewInstance();
Wrap(obj);
Ref(); // unref'd when the child isolate exits
obj->Set(String::NewSymbol("tid"),
Integer::New(isolate_->id_));
obj->Set(String::NewSymbol("send"),
FunctionTemplate::New(Send)->GetFunction());
}
~IsolateWrap() {
delete isolate_;
delete recv_channel_;
delete send_channel_;
}
Isolate* GetIsolate() {
return isolate_;
}
private:
// runs in the child thread
static void AtChildExit(void* arg) {
IsolateWrap* self = static_cast(arg);
uv_async_send(&self->child_exit_);
}
// runs in the parent thread
static void AfterChildExit(uv_async_t* handle, int status) {
IsolateWrap* self = container_of(handle, IsolateWrap, child_exit_);
self->OnExit();
}
void OnExit() {
if (uv_thread_join(&isolate_->tid_)) abort();
uv_close(reinterpret_cast(&child_exit_), NULL);
MakeCallback(handle_, "onexit", 0, NULL);
Unref(); // child is dead, it's safe to GC the JS object now
}
static void OnMessage(IsolateMessage* msg, void* arg) {
IsolateWrap* self = static_cast(arg);
self->OnMessage(msg);
}
void OnMessage(IsolateMessage* msg) {
NODE_ISOLATE_CHECK(parent_isolate_);
HandleScope scope;
Buffer* buf = Buffer::New(
msg->data_.buffer_, msg->data_.size_, IsolateMessage::Free, msg);
int argc = 1;
Handle argv[2] = {
buf->handle_
};
if (msg->flags & ISOLATEMESSAGE_SHARED_STREAM) {
// Instantiate the client javascript object and handle.
Local pending_obj = TCPWrap::Instantiate();
// Unwrap the client javascript object.
assert(pending_obj->InternalFieldCount() > 0);
TCPWrap* pending_wrap =
static_cast(pending_obj->GetPointerFromInternalField(0));
int r = uv_import(pending_wrap->GetStream(), &msg->shared_stream_info_);
assert(r == 0);
argv[1] = pending_obj;
argc++;
}
MakeCallback(handle_, "onmessage", argc, argv);
}
// TODO merge with Isolate::Send(), it's almost identical
static Handle Send(const Arguments& args) {
HandleScope scope;
IsolateWrap* self = Unwrap(args.This());
assert(Buffer::HasInstance(args[0]));
Local obj = args[0]->ToObject();
const char* data = Buffer::Data(obj);
size_t size = Buffer::Length(obj);
IsolateMessage* msg;
if (args[1]->IsObject()) {
uv_stream_info_t stream_info;
Local send_stream_obj = args[1]->ToObject();
assert(send_stream_obj->InternalFieldCount() > 0);
StreamWrap* send_stream_wrap = static_cast(
send_stream_obj->GetPointerFromInternalField(0));
uv_stream_t* send_stream = send_stream_wrap->GetStream();
int r = uv_export(send_stream, &stream_info);
assert(r == 0);
msg = new IsolateMessage(data, size, &stream_info);
} else {
msg = new IsolateMessage(data, size, NULL);
}
self->send_channel_->Send(msg);
return Undefined();
}
DISALLOW_IMPLICIT_CONSTRUCTORS(IsolateWrap);
Isolate* isolate_;
Isolate* parent_isolate_;
IsolateChannel* send_channel_;
IsolateChannel* recv_channel_;
uv_async_t child_exit_; // side effect: keeps the parent's event loop alive
// until the child exits
};
static void RunIsolate(void* arg) {
Isolate* isolate = static_cast(arg);
isolate->Enter();
StartThread(isolate, isolate->argc_, isolate->argv_);
isolate->Dispose();
}
static Handle CreateIsolate(const Arguments& args) {
HandleScope scope;
assert(args[0]->IsArray());
Local argv = args[0].As();
assert(argv->Length() >= 2);
Isolate* current_isolate = node::Isolate::GetCurrent();
IsolateWrap* wrap = new IsolateWrap(current_isolate);
Isolate* isolate = wrap->GetIsolate();
// Copy over arguments into isolate
isolate->argc_ = argv->Length();
isolate->argv_ = new char*[isolate->argc_ + 1];
for (int i = 0; i < isolate->argc_; ++i) {
String::Utf8Value str(argv->Get(i));
size_t size = 1 + strlen(*str);
isolate->argv_[i] = new char[size];
memcpy(isolate->argv_[i], *str, size);
}
isolate->argv_[isolate->argc_] = NULL;
// If options object was provided
if (args.Length() > 1) {
Local options = args[1].As();
Local opt_debug = options->Get(String::New("debug"));
Local opt_debug_brk = options->Get(String::New("debugBrk"));
// Handle .debug = true case
if (opt_debug->IsFunction()) {
isolate->debug_state = opt_debug_brk->IsTrue() ?
Isolate::kDebugBrk
:
Isolate::kDebug;
isolate->debugger_instance = IsolateDebugger::New(opt_debug);
}
}
if (uv_thread_create(&isolate->tid_, RunIsolate, isolate))
return Null(); // wrap is collected by the GC
else
return wrap->handle_;
}
static Handle CountIsolate(const Arguments& args) {
HandleScope scope;
return scope.Close(Integer::New(Isolate::Count()));
}
void InitIsolates(Handle target) {
HandleScope scope;
NODE_SET_METHOD(target, "create", CreateIsolate);
NODE_SET_METHOD(target, "count", CountIsolate);
IsolateDebugger::Initialize();
}
class IsolateDebuggerMessage {
public:
IsolateDebugger* d_;
uint16_t* value_;
int len_;
IsolateDebuggerMessage(IsolateDebugger* d, uint16_t* value, int len) {
d_ = d;
value_ = new uint16_t[len];
len_ = len;
memcpy(value_, value, len * sizeof(value_[0]));
}
~IsolateDebuggerMessage() {
delete[] value_;
}
};
void IsolateDebugger::Initialize() {
HandleScope scope;
Local t = FunctionTemplate::New(IsolateDebugger::New);
isolate_debugger_constructor = Persistent::New(t);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(String::NewSymbol("IsolateDebugger"));
NODE_SET_PROTOTYPE_METHOD(t, "write", IsolateDebugger::Write);
}
IsolateDebugger::IsolateDebugger(Handle init) {
debuggee_ = NULL;
initialized_ = false;
host_ = Isolate::GetCurrent();
host_loop_ = host_->GetLoop();
init_callback_fn_ = Persistent::New(init);
// Init async handle to invoke js callback once
// debugger will be initialized
uv_async_init(host_loop_,
&init_callback_,
IsolateDebugger::InitCallback);
init_callback_.data = reinterpret_cast(this);
msg_channel_ = new Channel(
host_loop_, MessageCallback, NULL);
}
IsolateDebugger::~IsolateDebugger() {
init_callback_fn_.Clear();
init_callback_fn_.Dispose();
delete msg_channel_;
}
void IsolateDebugger::Init(void) {
HandleScope scope;
Isolate* isolate = Isolate::GetCurrent();
debuggee_ = isolate;
debuggee_v8_ = isolate->GetV8Isolate();
v8::Debug::SetMessageHandler2(IsolateDebugger::DebugMessageHandler);
// Expose v8debug for isolate
if (isolate->debug_state == Isolate::kDebugBrk) {
Local debugContext = v8::Debug::GetDebugContext();
debugContext->SetSecurityToken(
isolate->GetV8Context()->GetSecurityToken()
);
isolate->GetV8Context()->Global()->Set(
String::New("v8debug"),
debugContext->Global()
);
}
initialized_ = true;
uv_async_send(&init_callback_);
}
void IsolateDebugger::InitCallback(uv_async_t* c, int status) {
assert(c->data != NULL);
IsolateDebugger* d = reinterpret_cast(c->data);
d->host_->Enter();
HandleScope scope;
Handle argv[1] = { d->handle_ };
Function::Cast(*d->init_callback_fn_)->Call(d->handle_, 1, argv);
d->host_->Exit();
// Unreference loop
uv_unref(d->host_loop_);
}
Handle IsolateDebugger::New(const Arguments& args) {
HandleScope scope;
IsolateDebugger* d = new IsolateDebugger(args[0]);
d->Wrap(args.Holder());
return args.This();
}
IsolateDebugger* IsolateDebugger::New(Handle init) {
HandleScope scope;
Handle argv[1] = { init };
Handle i = isolate_debugger_constructor->GetFunction()->NewInstance(
1,
argv
);
return ObjectWrap::Unwrap(i);
}
Handle IsolateDebugger::Write(const Arguments& args) {
HandleScope scope;
if (args.Length() != 1) {
return ThrowException(String::New(
"IsolateDebugger::Write requires one argument"
));
}
IsolateDebugger* d = ObjectWrap::Unwrap(args.This());
assert(d->initialized_);
String::Value v(args[0]->ToString());
v8::Debug::SendCommand(*v,
v.length(),
NULL,
d->debuggee_v8_);
return Undefined();
}
void IsolateDebugger::DebugMessageHandler(const v8::Debug::Message& message) {
IsolateDebugger* d = Isolate::GetCurrent()->debugger_instance;
String::Value v(message.GetJSON());
d->msg_channel_->Send(new IsolateDebuggerMessage(d, *v, v.length()));
}
void IsolateDebugger::MessageCallback(IsolateDebuggerMessage* msg, void*) {
assert(msg != NULL);
IsolateDebugger *d = msg->d_;
// Enter parent isolate context
d->host_->Enter();
HandleScope scope;
// debugger.onmessage should be a function!
Handle argv[] = { String::New(msg->value_, msg->len_) };
MakeCallback(d->handle_, "onmessage", ARRAY_SIZE(argv), argv);
// Free memory allocated for message
delete msg;
// And leave isolate
d->host_->Exit();
}
} // namespace node
NODE_MODULE(node_isolates, node::InitIsolates)