inspector: fix process._debugEnd() for inspector

This change ensures that the WebSocket server can be stopped
(and restarted if needed) buy calling process._debugEnd.

PR-URL: https://github.com/nodejs/node/pull/12777
Fixes: https://github.com/nodejs/node/issues/12559
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
This commit is contained in:
Eugene Ostroukhov 2017-05-01 13:31:14 -07:00
parent 3429c90f42
commit 5c26378cb7
11 changed files with 385 additions and 148 deletions

View File

@ -38,6 +38,18 @@ using v8_inspector::V8Inspector;
static uv_sem_t inspector_io_thread_semaphore; static uv_sem_t inspector_io_thread_semaphore;
static uv_async_t start_inspector_thread_async; static uv_async_t start_inspector_thread_async;
class StartIoTask : public v8::Task {
public:
explicit StartIoTask(Agent* agent) : agent(agent) {}
void Run() override {
agent->StartIoThread(false);
}
private:
Agent* agent;
};
std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate, std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
Local<Value> value) { Local<Value> value) {
TwoByteValue buffer(isolate, value); TwoByteValue buffer(isolate, value);
@ -46,9 +58,14 @@ std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
// Called from the main thread. // Called from the main thread.
void StartInspectorIoThreadAsyncCallback(uv_async_t* handle) { void StartInspectorIoThreadAsyncCallback(uv_async_t* handle) {
static_cast<Agent*>(handle->data)->StartIoThread(); static_cast<Agent*>(handle->data)->StartIoThread(false);
} }
void StartIoCallback(Isolate* isolate, void* agent) {
static_cast<Agent*>(agent)->StartIoThread(false);
}
#ifdef __POSIX__ #ifdef __POSIX__
static void EnableInspectorIOThreadSignalHandler(int signo) { static void EnableInspectorIOThreadSignalHandler(int signo) {
uv_sem_post(&inspector_io_thread_semaphore); uv_sem_post(&inspector_io_thread_semaphore);
@ -57,7 +74,9 @@ static void EnableInspectorIOThreadSignalHandler(int signo) {
inline void* InspectorIoThreadSignalThreadMain(void* unused) { inline void* InspectorIoThreadSignalThreadMain(void* unused) {
for (;;) { for (;;) {
uv_sem_wait(&inspector_io_thread_semaphore); uv_sem_wait(&inspector_io_thread_semaphore);
uv_async_send(&start_inspector_thread_async); Agent* agent = static_cast<Agent*>(start_inspector_thread_async.data);
if (agent != nullptr)
agent->RequestIoStart();
} }
return nullptr; return nullptr;
} }
@ -103,7 +122,9 @@ static int RegisterDebugSignalHandler() {
#ifdef _WIN32 #ifdef _WIN32
DWORD WINAPI EnableDebugThreadProc(void* arg) { DWORD WINAPI EnableDebugThreadProc(void* arg) {
uv_async_send(&start_inspector_thread_async); Agent* agent = static_cast<Agent*>(start_inspector_thread_async.data);
if (agent != nullptr)
agent->RequestIoStart();
return 0; return 0;
} }
@ -387,9 +408,6 @@ bool Agent::Start(v8::Platform* platform, const char* path,
new NodeInspectorClient(parent_env_, platform)); new NodeInspectorClient(parent_env_, platform));
inspector_->contextCreated(parent_env_->context(), "Node.js Main Context"); inspector_->contextCreated(parent_env_->context(), "Node.js Main Context");
platform_ = platform; platform_ = platform;
if (options.inspector_enabled()) {
return StartIoThread();
} else {
CHECK_EQ(0, uv_async_init(uv_default_loop(), CHECK_EQ(0, uv_async_init(uv_default_loop(),
&start_inspector_thread_async, &start_inspector_thread_async,
StartInspectorIoThreadAsyncCallback)); StartInspectorIoThreadAsyncCallback));
@ -397,11 +415,13 @@ bool Agent::Start(v8::Platform* platform, const char* path,
uv_unref(reinterpret_cast<uv_handle_t*>(&start_inspector_thread_async)); uv_unref(reinterpret_cast<uv_handle_t*>(&start_inspector_thread_async));
RegisterDebugSignalHandler(); RegisterDebugSignalHandler();
return true; if (options.inspector_enabled()) {
return StartIoThread(options.wait_for_connect());
} }
return true;
} }
bool Agent::StartIoThread() { bool Agent::StartIoThread(bool wait_for_connect) {
if (io_ != nullptr) if (io_ != nullptr)
return true; return true;
@ -409,7 +429,8 @@ bool Agent::StartIoThread() {
enabled_ = true; enabled_ = true;
io_ = std::unique_ptr<InspectorIo>( io_ = std::unique_ptr<InspectorIo>(
new InspectorIo(parent_env_, platform_, path_, debug_options_)); new InspectorIo(parent_env_, platform_, path_, debug_options_,
wait_for_connect));
if (!io_->Start()) { if (!io_->Start()) {
inspector_.reset(); inspector_.reset();
return false; return false;
@ -440,8 +461,10 @@ bool Agent::StartIoThread() {
} }
void Agent::Stop() { void Agent::Stop() {
if (io_ != nullptr) if (io_ != nullptr) {
io_->Stop(); io_->Stop();
io_.reset();
}
} }
void Agent::Connect(InspectorSessionDelegate* delegate) { void Agent::Connect(InspectorSessionDelegate* delegate) {
@ -502,6 +525,18 @@ void Agent::InitJSBindings(Local<Object> target, Local<Value> unused,
if (agent->debug_options_.wait_for_connect()) if (agent->debug_options_.wait_for_connect())
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart); env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
} }
void Agent::RequestIoStart() {
// We need to attempt to interrupt V8 flow (in case Node is running
// continuous JS code) and to wake up libuv thread (in case Node is wating
// for IO events)
uv_async_send(&start_inspector_thread_async);
v8::Isolate* isolate = parent_env_->isolate();
platform_->CallOnForegroundThread(isolate, new StartIoTask(this));
isolate->RequestInterrupt(StartIoCallback, this);
uv_async_send(&start_inspector_thread_async);
}
} // namespace inspector } // namespace inspector
} // namespace node } // namespace node

View File

@ -51,7 +51,6 @@ class Agent {
bool Start(v8::Platform* platform, const char* path, bool Start(v8::Platform* platform, const char* path,
const DebugOptions& options); const DebugOptions& options);
bool StartIoThread();
void Stop(); void Stop();
bool IsStarted(); bool IsStarted();
@ -72,6 +71,13 @@ class Agent {
v8::Local<v8::Context> context, v8::Local<v8::Context> context,
void* priv); void* priv);
bool StartIoThread(bool wait_for_connect);
InspectorIo* io() {
return io_.get();
}
// Can be called from any thread
void RequestIoStart();
private: private:
node::Environment* parent_env_; node::Environment* parent_env_;
std::unique_ptr<NodeInspectorClient> inspector_; std::unique_ptr<NodeInspectorClient> inspector_;

View File

@ -20,6 +20,7 @@
namespace node { namespace node {
namespace inspector { namespace inspector {
namespace { namespace {
using AsyncAndAgent = std::pair<uv_async_t, Agent*>;
using v8_inspector::StringBuffer; using v8_inspector::StringBuffer;
using v8_inspector::StringView; using v8_inspector::StringView;
@ -96,6 +97,12 @@ int CloseAsyncAndLoop(uv_async_t* async) {
return uv_loop_close(async->loop); return uv_loop_close(async->loop);
} }
void ReleasePairOnAsyncClose(uv_handle_t* async) {
AsyncAndAgent* pair = node::ContainerOf(&AsyncAndAgent::first,
reinterpret_cast<uv_async_t*>(async));
delete pair;
}
} // namespace } // namespace
std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string& message) { std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string& message) {
@ -127,6 +134,9 @@ class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
std::string GetTargetTitle(const std::string& id) override; std::string GetTargetTitle(const std::string& id) override;
std::string GetTargetUrl(const std::string& id) override; std::string GetTargetUrl(const std::string& id) override;
bool IsConnected() { return connected_; } bool IsConnected() { return connected_; }
void ServerDone() override {
io_->ServerDone();
}
private: private:
InspectorIo* io_; InspectorIo* io_;
bool connected_; bool connected_;
@ -137,53 +147,70 @@ class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
bool waiting_; bool waiting_;
}; };
void InterruptCallback(v8::Isolate*, void* io) { void InterruptCallback(v8::Isolate*, void* agent) {
static_cast<InspectorIo*>(io)->DispatchMessages(); InspectorIo* io = static_cast<Agent*>(agent)->io();
if (io != nullptr)
io->DispatchMessages();
} }
class DispatchOnInspectorBackendTask : public v8::Task { class DispatchMessagesTask : public v8::Task {
public: public:
explicit DispatchOnInspectorBackendTask(InspectorIo* io) : io_(io) {} explicit DispatchMessagesTask(Agent* agent) : agent_(agent) {}
void Run() override { void Run() override {
io_->DispatchMessages(); InspectorIo* io = agent_->io();
if (io != nullptr)
io->DispatchMessages();
} }
private: private:
InspectorIo* io_; Agent* agent_;
}; };
InspectorIo::InspectorIo(Environment* env, v8::Platform* platform, InspectorIo::InspectorIo(Environment* env, v8::Platform* platform,
const std::string& path, const DebugOptions& options) const std::string& path, const DebugOptions& options,
bool wait_for_connect)
: options_(options), thread_(), delegate_(nullptr), : options_(options), thread_(), delegate_(nullptr),
shutting_down_(false), state_(State::kNew), state_(State::kNew), parent_env_(env),
parent_env_(env), io_thread_req_(), io_thread_req_(), platform_(platform),
platform_(platform), dispatching_messages_(false), dispatching_messages_(false), session_id_(0),
session_id_(0), script_name_(path) { script_name_(path),
CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_, wait_for_connect_(wait_for_connect) {
main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()});
CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first,
InspectorIo::MainThreadAsyncCb)); InspectorIo::MainThreadAsyncCb));
uv_unref(reinterpret_cast<uv_handle_t*>(&main_thread_req_)); uv_unref(reinterpret_cast<uv_handle_t*>(&main_thread_req_->first));
CHECK_EQ(0, uv_sem_init(&start_sem_, 0)); CHECK_EQ(0, uv_sem_init(&start_sem_, 0));
} }
InspectorIo::~InspectorIo() {
uv_sem_destroy(&start_sem_);
uv_close(reinterpret_cast<uv_handle_t*>(&main_thread_req_->first),
ReleasePairOnAsyncClose);
}
bool InspectorIo::Start() { bool InspectorIo::Start() {
CHECK_EQ(state_, State::kNew);
CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadCbIO, this), 0); CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadCbIO, this), 0);
uv_sem_wait(&start_sem_); uv_sem_wait(&start_sem_);
if (state_ == State::kError) { if (state_ == State::kError) {
Stop();
return false; return false;
} }
state_ = State::kAccepting; state_ = State::kAccepting;
if (options_.wait_for_connect()) { if (wait_for_connect_) {
DispatchMessages(); DispatchMessages();
} }
return true; return true;
} }
void InspectorIo::Stop() { void InspectorIo::Stop() {
CHECK(state_ == State::kAccepting || state_ == State::kConnected);
Write(TransportAction::kKill, 0, StringView());
int err = uv_thread_join(&thread_); int err = uv_thread_join(&thread_);
CHECK_EQ(err, 0); CHECK_EQ(err, 0);
state_ = State::kShutDown;
DispatchMessages();
} }
bool InspectorIo::IsConnected() { bool InspectorIo::IsConnected() {
@ -195,8 +222,10 @@ bool InspectorIo::IsStarted() {
} }
void InspectorIo::WaitForDisconnect() { void InspectorIo::WaitForDisconnect() {
if (state_ == State::kAccepting)
state_ = State::kDone;
if (state_ == State::kConnected) { if (state_ == State::kConnected) {
shutting_down_ = true; state_ = State::kShutDown;
Write(TransportAction::kStop, 0, StringView()); Write(TransportAction::kStop, 0, StringView());
fprintf(stderr, "Waiting for the debugger to disconnect...\n"); fprintf(stderr, "Waiting for the debugger to disconnect...\n");
fflush(stderr); fflush(stderr);
@ -222,6 +251,9 @@ void InspectorIo::WriteCbIO(uv_async_t* async) {
io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_messages); io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_messages);
for (const auto& outgoing : outgoing_messages) { for (const auto& outgoing : outgoing_messages) {
switch (std::get<0>(outgoing)) { switch (std::get<0>(outgoing)) {
case TransportAction::kKill:
io_and_transport->first->TerminateConnections();
// Fallthrough
case TransportAction::kStop: case TransportAction::kStop:
io_and_transport->first->Stop(nullptr); io_and_transport->first->Stop(nullptr);
break; break;
@ -253,7 +285,7 @@ void InspectorIo::WorkerRunIO() {
uv_fs_req_cleanup(&req); uv_fs_req_cleanup(&req);
} }
InspectorIoDelegate delegate(this, script_path, script_name_, InspectorIoDelegate delegate(this, script_path, script_name_,
options_.wait_for_connect()); wait_for_connect_);
delegate_ = &delegate; delegate_ = &delegate;
InspectorSocketServer server(&delegate, InspectorSocketServer server(&delegate,
options_.host_name(), options_.host_name(),
@ -266,14 +298,12 @@ void InspectorIo::WorkerRunIO() {
uv_sem_post(&start_sem_); uv_sem_post(&start_sem_);
return; return;
} }
if (!options_.wait_for_connect()) { if (!wait_for_connect_) {
uv_sem_post(&start_sem_); uv_sem_post(&start_sem_);
} }
uv_run(&loop, UV_RUN_DEFAULT); uv_run(&loop, UV_RUN_DEFAULT);
io_thread_req_.data = nullptr; io_thread_req_.data = nullptr;
server.Stop(nullptr); CHECK_EQ(uv_loop_close(&loop), 0);
server.TerminateConnections(nullptr);
CHECK_EQ(CloseAsyncAndLoop(&io_thread_req_), 0);
delegate_ = nullptr; delegate_ = nullptr;
} }
@ -298,11 +328,12 @@ void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id,
const std::string& message) { const std::string& message) {
if (AppendMessage(&incoming_message_queue_, action, session_id, if (AppendMessage(&incoming_message_queue_, action, session_id,
Utf8ToStringView(message))) { Utf8ToStringView(message))) {
Agent* agent = main_thread_req_->second;
v8::Isolate* isolate = parent_env_->isolate(); v8::Isolate* isolate = parent_env_->isolate();
platform_->CallOnForegroundThread(isolate, platform_->CallOnForegroundThread(isolate,
new DispatchOnInspectorBackendTask(this)); new DispatchMessagesTask(agent));
isolate->RequestInterrupt(InterruptCallback, this); isolate->RequestInterrupt(InterruptCallback, agent);
CHECK_EQ(0, uv_async_send(&main_thread_req_)); CHECK_EQ(0, uv_async_send(&main_thread_req_->first));
} }
NotifyMessageReceived(); NotifyMessageReceived();
} }
@ -344,7 +375,7 @@ void InspectorIo::DispatchMessages() {
break; break;
case InspectorAction::kEndSession: case InspectorAction::kEndSession:
CHECK_NE(session_delegate_, nullptr); CHECK_NE(session_delegate_, nullptr);
if (shutting_down_) { if (state_ == State::kShutDown) {
state_ = State::kDone; state_ = State::kDone;
} else { } else {
state_ = State::kAccepting; state_ = State::kAccepting;
@ -363,12 +394,18 @@ void InspectorIo::DispatchMessages() {
// static // static
void InspectorIo::MainThreadAsyncCb(uv_async_t* req) { void InspectorIo::MainThreadAsyncCb(uv_async_t* req) {
InspectorIo* io = node::ContainerOf(&InspectorIo::main_thread_req_, req); AsyncAndAgent* pair = node::ContainerOf(&AsyncAndAgent::first, req);
// Note that this may be called after io was closed or even after a new
// one was created and ran.
InspectorIo* io = pair->second->io();
if (io != nullptr)
io->DispatchMessages(); io->DispatchMessages();
} }
void InspectorIo::Write(TransportAction action, int session_id, void InspectorIo::Write(TransportAction action, int session_id,
const StringView& inspector_message) { const StringView& inspector_message) {
if (state_ == State::kShutDown)
return;
AppendMessage(&outgoing_message_queue_, action, session_id, AppendMessage(&outgoing_message_queue_, action, session_id,
StringBuffer::create(inspector_message)); StringBuffer::create(inspector_message));
int err = uv_async_send(&io_thread_req_); int err = uv_async_send(&io_thread_req_);

View File

@ -31,18 +31,25 @@ namespace inspector {
class InspectorIoDelegate; class InspectorIoDelegate;
enum class InspectorAction { enum class InspectorAction {
kStartSession, kEndSession, kSendMessage kStartSession,
kEndSession,
kSendMessage
}; };
// kKill closes connections and stops the server, kStop only stops the server
enum class TransportAction { enum class TransportAction {
kSendMessage, kStop kKill,
kSendMessage,
kStop
}; };
class InspectorIo { class InspectorIo {
public: public:
InspectorIo(node::Environment* env, v8::Platform* platform, InspectorIo(node::Environment* env, v8::Platform* platform,
const std::string& path, const DebugOptions& options); const std::string& path, const DebugOptions& options,
bool wait_for_connect);
~InspectorIo();
// Start the inspector agent thread // Start the inspector agent thread
bool Start(); bool Start();
// Stop the inspector agent // Stop the inspector agent
@ -57,13 +64,23 @@ class InspectorIo {
void ResumeStartup() { void ResumeStartup() {
uv_sem_post(&start_sem_); uv_sem_post(&start_sem_);
} }
void ServerDone() {
uv_close(reinterpret_cast<uv_handle_t*>(&io_thread_req_), nullptr);
}
private: private:
template <typename Action> template <typename Action>
using MessageQueue = using MessageQueue =
std::vector<std::tuple<Action, int, std::vector<std::tuple<Action, int,
std::unique_ptr<v8_inspector::StringBuffer>>>; std::unique_ptr<v8_inspector::StringBuffer>>>;
enum class State { kNew, kAccepting, kConnected, kDone, kError }; enum class State {
kNew,
kAccepting,
kConnected,
kDone,
kError,
kShutDown
};
static void ThreadCbIO(void* agent); static void ThreadCbIO(void* agent);
static void MainThreadAsyncCb(uv_async_t* req); static void MainThreadAsyncCb(uv_async_t* req);
@ -94,12 +111,13 @@ class InspectorIo {
uv_thread_t thread_; uv_thread_t thread_;
InspectorIoDelegate* delegate_; InspectorIoDelegate* delegate_;
bool shutting_down_;
State state_; State state_;
node::Environment* parent_env_; node::Environment* parent_env_;
uv_async_t io_thread_req_; uv_async_t io_thread_req_;
uv_async_t main_thread_req_; // Note that this will live while the async is being closed - likely, past
// the parent object lifespan
std::pair<uv_async_t, Agent*>* main_thread_req_;
std::unique_ptr<InspectorSessionDelegate> session_delegate_; std::unique_ptr<InspectorSessionDelegate> session_delegate_;
v8::Platform* platform_; v8::Platform* platform_;
MessageQueue<InspectorAction> incoming_message_queue_; MessageQueue<InspectorAction> incoming_message_queue_;
@ -110,8 +128,9 @@ class InspectorIo {
std::string script_name_; std::string script_name_;
std::string script_path_; std::string script_path_;
const std::string id_; const std::string id_;
const bool wait_for_connect_;
friend class DispatchOnInspectorBackendTask; friend class DispatchMessagesTask;
friend class IoSessionDelegate; friend class IoSessionDelegate;
friend void InterruptCallback(v8::Isolate*, void* agent); friend void InterruptCallback(v8::Isolate*, void* agent);
}; };

View File

@ -212,7 +212,7 @@ class Closer {
class SocketSession { class SocketSession {
public: public:
SocketSession(InspectorSocketServer* server, int id); SocketSession(InspectorSocketServer* server, int id);
void Close(bool socket_cleanup, Closer* closer); void Close();
void Declined() { state_ = State::kDeclined; } void Declined() { state_ = State::kDeclined; }
static SocketSession* From(InspectorSocket* socket) { static SocketSession* From(InspectorSocket* socket) {
return node::ContainerOf(&SocketSession::socket_, socket); return node::ContainerOf(&SocketSession::socket_, socket);
@ -233,10 +233,8 @@ class SocketSession {
static void CloseCallback_(InspectorSocket* socket, int code); static void CloseCallback_(InspectorSocket* socket, int code);
static void ReadCallback_(uv_stream_t* stream, ssize_t read, static void ReadCallback_(uv_stream_t* stream, ssize_t read,
const uv_buf_t* buf); const uv_buf_t* buf);
void OnRemoteDataIO(InspectorSocket* socket, ssize_t read, void OnRemoteDataIO(ssize_t read, const uv_buf_t* buf);
const uv_buf_t* buf);
const int id_; const int id_;
Closer* closer_;
InspectorSocket socket_; InspectorSocket socket_;
InspectorSocketServer* server_; InspectorSocketServer* server_;
std::string target_id_; std::string target_id_;
@ -253,7 +251,9 @@ InspectorSocketServer::InspectorSocketServer(SocketServerDelegate* delegate,
server_(uv_tcp_t()), server_(uv_tcp_t()),
closer_(nullptr), closer_(nullptr),
next_session_id_(0), next_session_id_(0),
out_(out) { } out_(out) {
state_ = ServerState::kNew;
}
// static // static
@ -271,7 +271,7 @@ bool InspectorSocketServer::HandshakeCallback(InspectorSocket* socket,
SocketSession::From(socket)->FrontendConnected(); SocketSession::From(socket)->FrontendConnected();
return true; return true;
case kInspectorHandshakeFailed: case kInspectorHandshakeFailed:
SocketSession::From(socket)->Close(false, nullptr); server->SessionTerminated(SocketSession::From(socket));
return false; return false;
default: default:
UNREACHABLE(); UNREACHABLE();
@ -294,15 +294,21 @@ bool InspectorSocketServer::SessionStarted(SocketSession* session,
return connected; return connected;
} }
void InspectorSocketServer::SessionTerminated(int session_id) { void InspectorSocketServer::SessionTerminated(SocketSession* session) {
if (connected_sessions_.erase(session_id) == 0) { int id = session->Id();
return; if (connected_sessions_.erase(id) != 0) {
delegate_->EndSession(id);
if (connected_sessions_.empty()) {
if (state_ == ServerState::kRunning) {
PrintDebuggerReadyMessage(host_, port_,
delegate_->GetTargetIds(), out_);
} }
delegate_->EndSession(session_id); if (state_ == ServerState::kStopped) {
if (connected_sessions_.empty() && delegate_->ServerDone();
uv_is_active(reinterpret_cast<uv_handle_t*>(&server_))) {
PrintDebuggerReadyMessage(host_, port_, delegate_->GetTargetIds(), out_);
} }
}
}
delete session;
} }
bool InspectorSocketServer::RespondToGet(InspectorSocket* socket, bool InspectorSocketServer::RespondToGet(InspectorSocket* socket,
@ -369,6 +375,7 @@ void InspectorSocketServer::SendListResponse(InspectorSocket* socket) {
} }
bool InspectorSocketServer::Start(uv_loop_t* loop) { bool InspectorSocketServer::Start(uv_loop_t* loop) {
CHECK_EQ(state_, ServerState::kNew);
loop_ = loop; loop_ = loop;
sockaddr_in addr; sockaddr_in addr;
uv_tcp_init(loop_, &server_); uv_tcp_init(loop_, &server_);
@ -382,6 +389,7 @@ bool InspectorSocketServer::Start(uv_loop_t* loop) {
SocketConnectedCallback); SocketConnectedCallback);
} }
if (err == 0 && connected_sessions_.empty()) { if (err == 0 && connected_sessions_.empty()) {
state_ = ServerState::kRunning;
PrintDebuggerReadyMessage(host_, port_, delegate_->GetTargetIds(), out_); PrintDebuggerReadyMessage(host_, port_, delegate_->GetTargetIds(), out_);
} }
if (err != 0 && connected_sessions_.empty()) { if (err != 0 && connected_sessions_.empty()) {
@ -397,32 +405,21 @@ bool InspectorSocketServer::Start(uv_loop_t* loop) {
} }
void InspectorSocketServer::Stop(ServerCallback cb) { void InspectorSocketServer::Stop(ServerCallback cb) {
CHECK_EQ(state_, ServerState::kRunning);
if (closer_ == nullptr) { if (closer_ == nullptr) {
closer_ = new Closer(this); closer_ = new Closer(this);
} }
closer_->AddCallback(cb); closer_->AddCallback(cb);
uv_handle_t* handle = reinterpret_cast<uv_handle_t*>(&server_);
if (uv_is_active(handle)) {
closer_->IncreaseExpectedCount(); closer_->IncreaseExpectedCount();
state_ = ServerState::kStopping;
uv_close(reinterpret_cast<uv_handle_t*>(&server_), ServerClosedCallback); uv_close(reinterpret_cast<uv_handle_t*>(&server_), ServerClosedCallback);
}
closer_->NotifyIfDone(); closer_->NotifyIfDone();
} }
void InspectorSocketServer::TerminateConnections(ServerCallback cb) { void InspectorSocketServer::TerminateConnections() {
if (closer_ == nullptr) { for (const auto& session : connected_sessions_) {
closer_ = new Closer(this); session.second->Close();
} }
closer_->AddCallback(cb);
std::map<int, SocketSession*> sessions;
std::swap(sessions, connected_sessions_);
for (const auto& session : sessions) {
int id = session.second->Id();
session.second->Close(true, closer_);
delegate_->EndSession(id);
}
closer_->NotifyIfDone();
} }
bool InspectorSocketServer::TargetExists(const std::string& id) { bool InspectorSocketServer::TargetExists(const std::string& id) {
@ -441,8 +438,14 @@ void InspectorSocketServer::Send(int session_id, const std::string& message) {
// static // static
void InspectorSocketServer::ServerClosedCallback(uv_handle_t* server) { void InspectorSocketServer::ServerClosedCallback(uv_handle_t* server) {
InspectorSocketServer* socket_server = InspectorSocketServer::From(server); InspectorSocketServer* socket_server = InspectorSocketServer::From(server);
if (socket_server->closer_) CHECK_EQ(socket_server->state_, ServerState::kStopping);
if (socket_server->closer_) {
socket_server->closer_->DecreaseExpectedCount(); socket_server->closer_->DecreaseExpectedCount();
}
if (socket_server->connected_sessions_.empty()) {
socket_server->delegate_->ServerDone();
}
socket_server->state_ = ServerState::kStopped;
} }
// static // static
@ -461,32 +464,20 @@ void InspectorSocketServer::SocketConnectedCallback(uv_stream_t* server,
// InspectorSession tracking // InspectorSession tracking
SocketSession::SocketSession(InspectorSocketServer* server, int id) SocketSession::SocketSession(InspectorSocketServer* server, int id)
: id_(id), closer_(nullptr), server_(server), : id_(id), server_(server),
state_(State::kHttp) { } state_(State::kHttp) { }
void SocketSession::Close(bool socket_cleanup, Closer* closer) { void SocketSession::Close() {
CHECK_EQ(closer_, nullptr);
CHECK_NE(state_, State::kClosing); CHECK_NE(state_, State::kClosing);
server_->SessionTerminated(id_);
if (socket_cleanup) {
state_ = State::kClosing; state_ = State::kClosing;
closer_ = closer;
if (closer_ != nullptr)
closer->IncreaseExpectedCount();
inspector_close(&socket_, CloseCallback_); inspector_close(&socket_, CloseCallback_);
} else {
delete this;
}
} }
// static // static
void SocketSession::CloseCallback_(InspectorSocket* socket, int code) { void SocketSession::CloseCallback_(InspectorSocket* socket, int code) {
SocketSession* session = SocketSession::From(socket); SocketSession* session = SocketSession::From(socket);
CHECK_EQ(State::kClosing, session->state_); CHECK_EQ(State::kClosing, session->state_);
Closer* closer = session->closer_; session->server_->SessionTerminated(session);
if (closer != nullptr)
closer->DecreaseExpectedCount();
delete session;
} }
void SocketSession::FrontendConnected() { void SocketSession::FrontendConnected() {
@ -499,16 +490,14 @@ void SocketSession::FrontendConnected() {
void SocketSession::ReadCallback_(uv_stream_t* stream, ssize_t read, void SocketSession::ReadCallback_(uv_stream_t* stream, ssize_t read,
const uv_buf_t* buf) { const uv_buf_t* buf) {
InspectorSocket* socket = inspector_from_stream(stream); InspectorSocket* socket = inspector_from_stream(stream);
SocketSession::From(socket)->OnRemoteDataIO(socket, read, buf); SocketSession::From(socket)->OnRemoteDataIO(read, buf);
} }
void SocketSession::OnRemoteDataIO(InspectorSocket* socket, ssize_t read, void SocketSession::OnRemoteDataIO(ssize_t read, const uv_buf_t* buf) {
const uv_buf_t* buf) {
if (read > 0) { if (read > 0) {
server_->Delegate()->MessageReceived(id_, std::string(buf->base, read)); server_->Delegate()->MessageReceived(id_, std::string(buf->base, read));
} else { } else {
server_->SessionTerminated(id_); Close();
Close(true, nullptr);
} }
if (buf != nullptr && buf->base != nullptr) if (buf != nullptr && buf->base != nullptr)
delete[] buf->base; delete[] buf->base;

View File

@ -27,6 +27,7 @@ class SocketServerDelegate {
virtual std::vector<std::string> GetTargetIds() = 0; virtual std::vector<std::string> GetTargetIds() = 0;
virtual std::string GetTargetTitle(const std::string& id) = 0; virtual std::string GetTargetTitle(const std::string& id) = 0;
virtual std::string GetTargetUrl(const std::string& id) = 0; virtual std::string GetTargetUrl(const std::string& id) = 0;
virtual void ServerDone() = 0;
}; };
class InspectorSocketServer { class InspectorSocketServer {
@ -39,7 +40,7 @@ class InspectorSocketServer {
bool Start(uv_loop_t* loop); bool Start(uv_loop_t* loop);
void Stop(ServerCallback callback); void Stop(ServerCallback callback);
void Send(int session_id, const std::string& message); void Send(int session_id, const std::string& message);
void TerminateConnections(ServerCallback callback); void TerminateConnections();
int port() { int port() {
return port_; return port_;
} }
@ -59,11 +60,11 @@ class InspectorSocketServer {
void SendListResponse(InspectorSocket* socket); void SendListResponse(InspectorSocket* socket);
void ReadCallback(InspectorSocket* socket, ssize_t read, const uv_buf_t* buf); void ReadCallback(InspectorSocket* socket, ssize_t read, const uv_buf_t* buf);
bool SessionStarted(SocketSession* session, const std::string& id); bool SessionStarted(SocketSession* session, const std::string& id);
void SessionTerminated(int id); void SessionTerminated(SocketSession* session);
bool TargetExists(const std::string& id); bool TargetExists(const std::string& id);
static void SocketSessionDeleter(SocketSession*);
SocketServerDelegate* Delegate() { return delegate_; } SocketServerDelegate* Delegate() { return delegate_; }
enum class ServerState {kNew, kRunning, kStopping, kStopped};
uv_loop_t* loop_; uv_loop_t* loop_;
SocketServerDelegate* const delegate_; SocketServerDelegate* const delegate_;
const std::string host_; const std::string host_;
@ -74,6 +75,7 @@ class InspectorSocketServer {
std::map<int, SocketSession*> connected_sessions_; std::map<int, SocketSession*> connected_sessions_;
int next_session_id_; int next_session_id_;
FILE* out_; FILE* out_;
ServerState state_;
friend class SocketSession; friend class SocketSession;
friend class Closer; friend class Closer;

View File

@ -232,7 +232,6 @@ bool v8_initialized = false;
// process-relative uptime base, initialized at start-up // process-relative uptime base, initialized at start-up
static double prog_start_time; static double prog_start_time;
static bool debugger_running;
static Mutex node_isolate_mutex; static Mutex node_isolate_mutex;
static v8::Isolate* node_isolate; static v8::Isolate* node_isolate;
@ -261,6 +260,10 @@ static struct {
const node::DebugOptions& options) { const node::DebugOptions& options) {
return env->inspector_agent()->Start(platform_, script_path, options); return env->inspector_agent()->Start(platform_, script_path, options);
} }
bool InspectorStarted(Environment *env) {
return env->inspector_agent()->IsStarted();
}
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR
void StartTracingAgent() { void StartTracingAgent() {
@ -290,6 +293,9 @@ static struct {
"so event tracing is not available.\n"); "so event tracing is not available.\n");
} }
void StopTracingAgent() {} void StopTracingAgent() {}
bool InspectorStarted(Environment *env) {
return false;
}
#endif // !NODE_USE_V8_PLATFORM #endif // !NODE_USE_V8_PLATFORM
} v8_platform; } v8_platform;
@ -3972,9 +3978,9 @@ static void ParseArgs(int* argc,
static void StartDebug(Environment* env, const char* path, static void StartDebug(Environment* env, const char* path,
DebugOptions debug_options) { DebugOptions debug_options) {
CHECK(!debugger_running);
#if HAVE_INSPECTOR #if HAVE_INSPECTOR
debugger_running = v8_platform.StartInspector(env, path, debug_options); CHECK(!env->inspector_agent()->IsStarted());
v8_platform.StartInspector(env, path, debug_options);
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR
} }
@ -4119,13 +4125,12 @@ static void DebugPause(const FunctionCallbackInfo<Value>& args) {
static void DebugEnd(const FunctionCallbackInfo<Value>& args) { static void DebugEnd(const FunctionCallbackInfo<Value>& args) {
if (debugger_running) {
#if HAVE_INSPECTOR #if HAVE_INSPECTOR
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
if (env->inspector_agent()->IsStarted()) {
env->inspector_agent()->Stop(); env->inspector_agent()->Stop();
#endif
debugger_running = false;
} }
#endif
} }
@ -4462,7 +4467,7 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
const char* path = argc > 1 ? argv[1] : nullptr; const char* path = argc > 1 ? argv[1] : nullptr;
StartDebug(&env, path, debug_options); StartDebug(&env, path, debug_options);
if (debug_options.inspector_enabled() && !debugger_running) if (debug_options.inspector_enabled() && !v8_platform.InspectorStarted(&env))
return 12; // Signal internal error. return 12; // Signal internal error.
env.set_abort_on_uncaught_exception(abort_on_uncaught_exception); env.set_abort_on_uncaught_exception(abort_on_uncaught_exception);

View File

@ -85,7 +85,7 @@ class InspectorSocketServerTest : public ::testing::Test {
class TestInspectorServerDelegate : public SocketServerDelegate { class TestInspectorServerDelegate : public SocketServerDelegate {
public: public:
TestInspectorServerDelegate() : connected(0), disconnected(0), TestInspectorServerDelegate() : connected(0), disconnected(0), done(false),
targets_({ MAIN_TARGET_ID, targets_({ MAIN_TARGET_ID,
UNCONNECTABLE_TARGET_ID }), UNCONNECTABLE_TARGET_ID }),
session_id_(0) {} session_id_(0) {}
@ -139,8 +139,13 @@ class TestInspectorServerDelegate : public SocketServerDelegate {
server_->Send(session_id_, message); server_->Send(session_id_, message);
} }
void ServerDone() {
done = true;
}
int connected; int connected;
int disconnected; int disconnected;
bool done;
private: private:
const std::vector<std::string> targets_; const std::vector<std::string> targets_;
@ -302,7 +307,7 @@ class ServerHolder {
public: public:
template <typename Delegate> template <typename Delegate>
ServerHolder(Delegate* delegate, int port, FILE* out = NULL) ServerHolder(Delegate* delegate, int port, FILE* out = NULL)
: closed(false), paused(false), sessions_terminated(false), : closed(false), paused(false),
server_(delegate, HOST, port, out) { server_(delegate, HOST, port, out) {
delegate->Connect(&server_); delegate->Connect(&server_);
} }
@ -320,11 +325,6 @@ class ServerHolder {
holder->closed = true; holder->closed = true;
} }
static void ConnectionsTerminated(InspectorSocketServer* server) {
ServerHolder* holder = node::ContainerOf(&ServerHolder::server_, server);
holder->sessions_terminated = true;
}
static void PausedCallback(InspectorSocketServer* server) { static void PausedCallback(InspectorSocketServer* server) {
ServerHolder* holder = node::ContainerOf(&ServerHolder::server_, server); ServerHolder* holder = node::ContainerOf(&ServerHolder::server_, server);
holder->paused = true; holder->paused = true;
@ -332,7 +332,6 @@ class ServerHolder {
bool closed; bool closed;
bool paused; bool paused;
bool sessions_terminated;
private: private:
InspectorSocketServer server_; InspectorSocketServer server_;
@ -359,6 +358,12 @@ class ServerDelegateNoTargets : public SocketServerDelegate {
std::string GetTargetUrl(const std::string& id) override { std::string GetTargetUrl(const std::string& id) override {
return ""; return "";
} }
void ServerDone() override {
done = true;
}
bool done = false;
}; };
static void TestHttpRequest(int port, const std::string& path, static void TestHttpRequest(int port, const std::string& path,
@ -446,7 +451,7 @@ TEST_F(InspectorSocketServerTest, InspectorSessions) {
stays_till_termination_socket.Write(WsHandshakeRequest(MAIN_TARGET_ID)); stays_till_termination_socket.Write(WsHandshakeRequest(MAIN_TARGET_ID));
stays_till_termination_socket.Expect(WS_HANDSHAKE_RESPONSE); stays_till_termination_socket.Expect(WS_HANDSHAKE_RESPONSE);
EXPECT_EQ(3, delegate.connected); SPIN_WHILE(3 != delegate.connected);
delegate.Write("5678"); delegate.Write("5678");
stays_till_termination_socket.Expect("\x81\x4" "5678"); stays_till_termination_socket.Expect("\x81\x4" "5678");
@ -456,12 +461,12 @@ TEST_F(InspectorSocketServerTest, InspectorSessions) {
delegate.Expect("1234"); delegate.Expect("1234");
server->Stop(ServerHolder::CloseCallback); server->Stop(ServerHolder::CloseCallback);
server->TerminateConnections(ServerHolder::ConnectionsTerminated); server->TerminateConnections();
stays_till_termination_socket.Write(CLIENT_CLOSE_FRAME); stays_till_termination_socket.Write(CLIENT_CLOSE_FRAME);
stays_till_termination_socket.Expect(SERVER_CLOSE_FRAME); stays_till_termination_socket.Expect(SERVER_CLOSE_FRAME);
EXPECT_EQ(3, delegate.disconnected); SPIN_WHILE(3 != delegate.disconnected);
SPIN_WHILE(!server.closed); SPIN_WHILE(!server.closed);
stays_till_termination_socket.ExpectEOF(); stays_till_termination_socket.ExpectEOF();
@ -473,8 +478,9 @@ TEST_F(InspectorSocketServerTest, ServerDoesNothing) {
ASSERT_TRUE(server->Start(&loop)); ASSERT_TRUE(server->Start(&loop));
server->Stop(ServerHolder::CloseCallback); server->Stop(ServerHolder::CloseCallback);
server->TerminateConnections(ServerHolder::ConnectionsTerminated); server->TerminateConnections();
SPIN_WHILE(!server.closed); SPIN_WHILE(!server.closed);
ASSERT_TRUE(delegate.done);
} }
TEST_F(InspectorSocketServerTest, ServerWithoutTargets) { TEST_F(InspectorSocketServerTest, ServerWithoutTargets) {
@ -491,7 +497,7 @@ TEST_F(InspectorSocketServerTest, ServerWithoutTargets) {
socket.Expect("HTTP/1.0 400 Bad Request"); socket.Expect("HTTP/1.0 400 Bad Request");
socket.ExpectEOF(); socket.ExpectEOF();
server->Stop(ServerHolder::CloseCallback); server->Stop(ServerHolder::CloseCallback);
server->TerminateConnections(ServerHolder::ConnectionsTerminated); server->TerminateConnections();
SPIN_WHILE(!server.closed); SPIN_WHILE(!server.closed);
} }
@ -502,11 +508,9 @@ TEST_F(InspectorSocketServerTest, ServerCannotStart) {
ServerHolder server2(&delegate2, server1.port()); ServerHolder server2(&delegate2, server1.port());
ASSERT_FALSE(server2->Start(&loop)); ASSERT_FALSE(server2->Start(&loop));
server1->Stop(ServerHolder::CloseCallback); server1->Stop(ServerHolder::CloseCallback);
server1->TerminateConnections(ServerHolder::ConnectionsTerminated); server1->TerminateConnections();
server2->Stop(ServerHolder::CloseCallback);
server2->TerminateConnections(ServerHolder::ConnectionsTerminated);
SPIN_WHILE(!server1.closed); SPIN_WHILE(!server1.closed);
SPIN_WHILE(!server2.closed); ASSERT_TRUE(delegate1.done);
} }
TEST_F(InspectorSocketServerTest, StoppingServerDoesNotKillConnections) { TEST_F(InspectorSocketServerTest, StoppingServerDoesNotKillConnections) {
@ -521,4 +525,53 @@ TEST_F(InspectorSocketServerTest, StoppingServerDoesNotKillConnections) {
socket1.TestHttpRequest("/json/list", "[ ]"); socket1.TestHttpRequest("/json/list", "[ ]");
socket1.Close(); socket1.Close();
uv_run(&loop, UV_RUN_DEFAULT); uv_run(&loop, UV_RUN_DEFAULT);
ASSERT_TRUE(delegate.done);
}
TEST_F(InspectorSocketServerTest, ClosingConnectionReportsDone) {
ServerDelegateNoTargets delegate;
ServerHolder server(&delegate, 0);
ASSERT_TRUE(server->Start(&loop));
SocketWrapper socket1(&loop);
socket1.Connect(HOST, server.port());
socket1.TestHttpRequest("/json/list", "[ ]");
server->Stop(ServerHolder::CloseCallback);
SPIN_WHILE(!server.closed);
socket1.TestHttpRequest("/json/list", "[ ]");
socket1.Close();
uv_run(&loop, UV_RUN_DEFAULT);
ASSERT_TRUE(delegate.done);
}
TEST_F(InspectorSocketServerTest, ClosingSocketReportsDone) {
TestInspectorServerDelegate delegate;
ServerHolder server(&delegate, 0);
ASSERT_TRUE(server->Start(&loop));
SocketWrapper socket1(&loop);
socket1.Connect(HOST, server.port());
socket1.Write(WsHandshakeRequest(MAIN_TARGET_ID));
socket1.Expect(WS_HANDSHAKE_RESPONSE);
server->Stop(ServerHolder::CloseCallback);
SPIN_WHILE(!server.closed);
ASSERT_FALSE(delegate.done);
socket1.Close();
SPIN_WHILE(!delegate.done);
}
TEST_F(InspectorSocketServerTest, TerminatingSessionReportsDone) {
TestInspectorServerDelegate delegate;
ServerHolder server(&delegate, 0);
ASSERT_TRUE(server->Start(&loop));
SocketWrapper socket1(&loop);
socket1.Connect(HOST, server.port());
socket1.Write(WsHandshakeRequest(MAIN_TARGET_ID));
socket1.Expect(WS_HANDSHAKE_RESPONSE);
server->Stop(ServerHolder::CloseCallback);
SPIN_WHILE(!server.closed);
ASSERT_FALSE(delegate.done);
server->TerminateConnections();
socket1.Expect(SERVER_CLOSE_FRAME);
socket1.Write(CLIENT_CLOSE_FRAME);
socket1.ExpectEOF();
SPIN_WHILE(!delegate.done);
} }

View File

@ -8,9 +8,8 @@ const spawn = require('child_process').spawn;
const url = require('url'); const url = require('url');
const DEBUG = false; const DEBUG = false;
const TIMEOUT = 15 * 1000; const TIMEOUT = 15 * 1000;
const EXPECT_ALIVE_SYMBOL = Symbol('isAlive');
const mainScript = path.join(common.fixturesDir, 'loop.js'); const mainScript = path.join(common.fixturesDir, 'loop.js');
function send(socket, message, id, callback) { function send(socket, message, id, callback) {
@ -24,6 +23,7 @@ function send(socket, message, id, callback) {
wsHeaderBuf.writeUInt8(0x81, 0); wsHeaderBuf.writeUInt8(0x81, 0);
let byte2 = 0x80; let byte2 = 0x80;
const bodyLen = messageBuf.length; const bodyLen = messageBuf.length;
let maskOffset = 2; let maskOffset = 2;
if (bodyLen < 126) { if (bodyLen < 126) {
byte2 = 0x80 + bodyLen; byte2 = 0x80 + bodyLen;
@ -47,9 +47,17 @@ function send(socket, message, id, callback) {
callback); callback);
} }
function sendEnd(socket) {
socket.write(Buffer.from([0x88, 0x80, 0x2D, 0x0E, 0x1E, 0xFA]));
}
function parseWSFrame(buffer, handler) { function parseWSFrame(buffer, handler) {
if (buffer.length < 2) if (buffer.length < 2)
return 0; return 0;
if (buffer[0] === 0x88 && buffer[1] === 0x00) {
handler(null);
return 2;
}
assert.strictEqual(0x81, buffer[0]); assert.strictEqual(0x81, buffer[0]);
let dataLen = 0x7F & buffer[1]; let dataLen = 0x7F & buffer[1];
let bodyOffset = 2; let bodyOffset = 2;
@ -136,6 +144,7 @@ function TestSession(socket, harness) {
this.messages_ = {}; this.messages_ = {};
this.expectedId_ = 1; this.expectedId_ = 1;
this.lastMessageResponseCallback_ = null; this.lastMessageResponseCallback_ = null;
this.closeCallback_ = null;
let buffer = Buffer.alloc(0); let buffer = Buffer.alloc(0);
socket.on('data', (data) => { socket.on('data', (data) => {
@ -146,7 +155,10 @@ function TestSession(socket, harness) {
if (consumed) if (consumed)
buffer = buffer.slice(consumed); buffer = buffer.slice(consumed);
} while (consumed); } while (consumed);
}).on('close', () => assert(this.expectClose_, 'Socket closed prematurely')); }).on('close', () => {
assert(this.expectClose_, 'Socket closed prematurely');
this.closeCallback_ && this.closeCallback_();
});
} }
TestSession.prototype.scriptUrlForId = function(id) { TestSession.prototype.scriptUrlForId = function(id) {
@ -154,6 +166,11 @@ TestSession.prototype.scriptUrlForId = function(id) {
}; };
TestSession.prototype.processMessage_ = function(message) { TestSession.prototype.processMessage_ = function(message) {
if (message === null) {
sendEnd(this.socket_);
return;
}
const method = message['method']; const method = message['method'];
if (method === 'Debugger.scriptParsed') { if (method === 'Debugger.scriptParsed') {
const script = message['params']; const script = message['params'];
@ -222,6 +239,27 @@ TestSession.prototype.sendInspectorCommands = function(commands) {
}); });
}; };
TestSession.prototype.sendCommandsAndExpectClose = function(commands) {
if (!(commands instanceof Array))
commands = [commands];
return this.enqueue((callback) => {
let timeoutId = null;
let done = false;
this.expectClose_ = true;
this.closeCallback_ = function() {
if (timeoutId)
clearTimeout(timeoutId);
done = true;
callback();
};
this.sendAll_(commands, () => {
if (!done) {
timeoutId = timeout('Session still open');
}
});
});
};
TestSession.prototype.createCallbackWithTimeout_ = function(message) { TestSession.prototype.createCallbackWithTimeout_ = function(message) {
const promise = new Promise((resolve) => { const promise = new Promise((resolve) => {
this.enqueue((callback) => { this.enqueue((callback) => {
@ -285,11 +323,23 @@ TestSession.prototype.enqueue = function(task) {
TestSession.prototype.disconnect = function(childDone) { TestSession.prototype.disconnect = function(childDone) {
return this.enqueue((callback) => { return this.enqueue((callback) => {
this.expectClose_ = true; this.expectClose_ = true;
this.harness_.childInstanceDone =
this.harness_.childInstanceDone || childDone;
this.socket_.destroy(); this.socket_.destroy();
console.log('[test]', 'Connection terminated'); console.log('[test]', 'Connection terminated');
callback(); callback();
}, childDone);
};
TestSession.prototype.expectClose = function() {
return this.enqueue((callback) => {
this.expectClose_ = true;
callback();
});
};
TestSession.prototype.assertClosed = function() {
return this.enqueue((callback) => {
assert.strictEqual(this.closed_, true);
callback();
}); });
}; };
@ -307,8 +357,7 @@ function Harness(port, childProcess) {
this.mainScriptPath = mainScript; this.mainScriptPath = mainScript;
this.stderrFilters_ = []; this.stderrFilters_ = [];
this.process_ = childProcess; this.process_ = childProcess;
this.childInstanceDone = false; this.result_ = {};
this.returnCode_ = null;
this.running_ = true; this.running_ = true;
childProcess.stdout.on('data', makeBufferingDataCallback( childProcess.stdout.on('data', makeBufferingDataCallback(
@ -323,8 +372,7 @@ function Harness(port, childProcess) {
this.stderrFilters_ = pending; this.stderrFilters_ = pending;
})); }));
childProcess.on('exit', (code, signal) => { childProcess.on('exit', (code, signal) => {
assert(this.childInstanceDone, 'Child instance died prematurely'); this.result_ = {code, signal};
this.returnCode_ = code;
this.running_ = false; this.running_ = false;
}); });
} }
@ -338,8 +386,15 @@ Harness.prototype.addStderrFilter = function(regexp, callback) {
}); });
}; };
Harness.prototype.assertStillAlive = function() {
assert.strictEqual(this.running_, true,
'Child died: ' + JSON.stringify(this.result_));
};
Harness.prototype.run_ = function() { Harness.prototype.run_ = function() {
setImmediate(() => { setImmediate(() => {
if (!this.task_[EXPECT_ALIVE_SYMBOL])
this.assertStillAlive();
this.task_(() => { this.task_(() => {
this.task_ = this.task_.next_; this.task_ = this.task_.next_;
if (this.task_) if (this.task_)
@ -348,7 +403,8 @@ Harness.prototype.run_ = function() {
}); });
}; };
Harness.prototype.enqueue_ = function(task) { Harness.prototype.enqueue_ = function(task, expectAlive) {
task[EXPECT_ALIVE_SYMBOL] = !!expectAlive;
if (!this.task_) { if (!this.task_) {
this.task_ = task; this.task_ = task;
this.run_(); this.run_();
@ -420,16 +476,16 @@ Harness.prototype.expectShutDown = function(errorCode) {
this.enqueue_((callback) => { this.enqueue_((callback) => {
if (this.running_) { if (this.running_) {
const timeoutId = timeout('Have not terminated'); const timeoutId = timeout('Have not terminated');
this.process_.on('exit', (code) => { this.process_.on('exit', (code, signal) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
assert.strictEqual(errorCode, code); assert.strictEqual(errorCode, code, JSON.stringify({code, signal}));
callback(); callback();
}); });
} else { } else {
assert.strictEqual(errorCode, this.returnCode_); assert.strictEqual(errorCode, this.result_.code);
callback(); callback();
} }
}); }, true);
}; };
Harness.prototype.kill = function() { Harness.prototype.kill = function() {

View File

@ -0,0 +1,11 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const helper = require('./inspector-helper.js');
function testStop(harness) {
harness.expectShutDown(42);
}
helper.startNodeForInspectorTest(testStop, '--inspect',
'process._debugEnd();process.exit(42);');

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const helper = require('./inspector-helper.js');
function testResume(session) {
session.sendCommandsAndExpectClose([
{ 'method': 'Runtime.runIfWaitingForDebugger' }
]);
}
function testDisconnectSession(harness) {
harness
.runFrontendSession([
testResume,
]).expectShutDown(42);
}
const script = 'process._debugEnd();' +
'process._debugProcess(process.pid);' +
'setTimeout(() => {console.log("Done");process.exit(42)});';
helper.startNodeForInspectorTest(testDisconnectSession, '--inspect-brk',
script);