From 23119cacf83e7e8010b4f6600fdcbcafb12dfae4 Mon Sep 17 00:00:00 2001 From: Aleksei Koziatinskii Date: Tue, 14 May 2019 18:13:48 -0700 Subject: [PATCH] inspector: supported NodeRuntime domain in worker NodeRuntime domain was introduced to give inspector client way to fetch captured information before Node process is gone. We need similar capability for work. With current protocol inspector client can force worker to wait on start by passing waitForDebuggerOnStart flag to NodeWorker.enable method. So client has some time to setup environment, e.g. start profiler. At the same time there is no way to prevent worker from being terminated. So we can start capturing profile but we can not reliably get captured data back. This PR implemented NodeRuntime.notifyWhenWaitingForDisconnect method for worker. When NodeRuntime.waitingForDisconnect notification is enabled, worker will wait for explicit NodeWorker.detach call. With this PR worker tooling story is nicely aligned with main thread tooling story. The only difference is that main thread by default is waiting for disconnect but worker thread is not waiting. Issue: https://github.com/nodejs/node/issues/27677 PR-URL: https://github.com/nodejs/node/pull/27706 Reviewed-By: Eugene Ostroukhov --- src/inspector/node_protocol.pdl | 5 ++++ src/inspector/runtime_agent.cc | 4 --- src/inspector/worker_agent.cc | 5 ++++ src/inspector/worker_agent.h | 1 + src/inspector_agent.cc | 18 ++++++++---- test/parallel/test-worker-debug.js | 47 ++++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/inspector/node_protocol.pdl b/src/inspector/node_protocol.pdl index 36d528b937a038..608521b467d9e4 100644 --- a/src/inspector/node_protocol.pdl +++ b/src/inspector/node_protocol.pdl @@ -71,6 +71,11 @@ experimental domain NodeWorker # Detaches from all running workers and disables attaching to new workers as they are started. command disable + # Detached from the worker with given sessionId. + command detach + parameters + SessionID sessionId + # Issued when attached to a worker. event attachedToWorker parameters diff --git a/src/inspector/runtime_agent.cc b/src/inspector/runtime_agent.cc index f8fdbe42d41e03..4056140e703493 100644 --- a/src/inspector/runtime_agent.cc +++ b/src/inspector/runtime_agent.cc @@ -16,10 +16,6 @@ void RuntimeAgent::Wire(UberDispatcher* dispatcher) { } DispatchResponse RuntimeAgent::notifyWhenWaitingForDisconnect(bool enabled) { - if (!env_->owns_process_state()) { - return DispatchResponse::Error( - "NodeRuntime domain can only be used through main thread sessions"); - } notify_when_waiting_for_disconnect_ = enabled; return DispatchResponse::OK(); } diff --git a/src/inspector/worker_agent.cc b/src/inspector/worker_agent.cc index d343de8494a36f..81706572e646ea 100644 --- a/src/inspector/worker_agent.cc +++ b/src/inspector/worker_agent.cc @@ -115,6 +115,11 @@ DispatchResponse WorkerAgent::disable() { return DispatchResponse::OK(); } +DispatchResponse WorkerAgent::detach(const String& sessionId) { + workers_->Detached(sessionId); + return DispatchResponse::OK(); +} + void NodeWorkers::WorkerCreated(const std::string& title, const std::string& url, bool waiting, diff --git a/src/inspector/worker_agent.h b/src/inspector/worker_agent.h index 402c7194163967..1bd25189bf3026 100644 --- a/src/inspector/worker_agent.h +++ b/src/inspector/worker_agent.h @@ -25,6 +25,7 @@ class WorkerAgent : public NodeWorker::Backend { DispatchResponse enable(bool waitForDebuggerOnStart) override; DispatchResponse disable() override; + DispatchResponse detach(const String& sessionId) override; private: std::shared_ptr frontend_; diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index b44d27254d5b54..8a12c2dddf7493 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -472,8 +472,8 @@ class NodeInspectorClient : public V8InspectorClient { runMessageLoop(); } - void waitForIoShutdown() { - waiting_for_io_shutdown_ = true; + void waitForSessionsDisconnect() { + waiting_for_sessions_disconnect_ = true; runMessageLoop(); } @@ -548,6 +548,8 @@ class NodeInspectorClient : public V8InspectorClient { } contextDestroyed(env_->context()); } + if (waiting_for_sessions_disconnect_ && !is_main_) + waiting_for_sessions_disconnect_ = false; } void dispatchMessageFromFrontend(int session_id, const StringView& message) { @@ -678,8 +680,9 @@ class NodeInspectorClient : public V8InspectorClient { bool shouldRunMessageLoop() { if (waiting_for_frontend_) return true; - if (waiting_for_io_shutdown_ || waiting_for_resume_) + if (waiting_for_sessions_disconnect_ || waiting_for_resume_) { return hasConnectedSessions(); + } return false; } @@ -723,7 +726,7 @@ class NodeInspectorClient : public V8InspectorClient { int next_session_id_ = 1; bool waiting_for_resume_ = false; bool waiting_for_frontend_ = false; - bool waiting_for_io_shutdown_ = false; + bool waiting_for_sessions_disconnect_ = false; // Allows accessing Inspector from non-main threads std::unique_ptr interface_; std::shared_ptr worker_manager_; @@ -819,11 +822,14 @@ void Agent::WaitForDisconnect() { fprintf(stderr, "Waiting for the debugger to disconnect...\n"); fflush(stderr); } - if (!client_->notifyWaitingForDisconnect()) + if (!client_->notifyWaitingForDisconnect()) { client_->contextDestroyed(parent_env_->context()); + } else if (is_worker) { + client_->waitForSessionsDisconnect(); + } if (io_ != nullptr) { io_->StopAcceptingNewConnections(); - client_->waitForIoShutdown(); + client_->waitForSessionsDisconnect(); } } diff --git a/test/parallel/test-worker-debug.js b/test/parallel/test-worker-debug.js index 8726d2a031ee32..4906f623e85ef1 100644 --- a/test/parallel/test-worker-debug.js +++ b/test/parallel/test-worker-debug.js @@ -206,6 +206,52 @@ async function testTwoWorkers(session, post) { await Promise.all([worker1Exited, worker2Exited]); } +async function testWaitForDisconnectInWorker(session, post) { + console.log('Test NodeRuntime.waitForDisconnect in worker'); + + const sessionWithoutWaiting = new Session(); + sessionWithoutWaiting.connect(); + const sessionWithoutWaitingPost = doPost.bind(null, sessionWithoutWaiting); + + await sessionWithoutWaitingPost('NodeWorker.enable', { + waitForDebuggerOnStart: true + }); + await post('NodeWorker.enable', { waitForDebuggerOnStart: true }); + + const attached = [ + waitForWorkerAttach(session), + waitForWorkerAttach(sessionWithoutWaiting) + ]; + + let worker = null; + const exitPromise = runWorker(2, (w) => worker = w); + + const [{ sessionId: sessionId1 }, { sessionId: sessionId2 }] = + await Promise.all(attached); + + const workerSession1 = new WorkerSession(session, sessionId1); + const workerSession2 = new WorkerSession(sessionWithoutWaiting, sessionId2); + + await workerSession2.post('Runtime.enable'); + await workerSession1.post('Runtime.enable'); + await workerSession1.post('NodeRuntime.notifyWhenWaitingForDisconnect', { + enabled: true + }); + await workerSession1.post('Runtime.runIfWaitingForDebugger'); + + worker.postMessage('resume'); + + await waitForEvent(workerSession1, 'NodeRuntime.waitingForDisconnect'); + post('NodeWorker.detach', { sessionId: sessionId1 }); + await waitForEvent(workerSession2, 'Runtime.executionContextDestroyed'); + + await exitPromise; + + await post('NodeWorker.disable'); + await sessionWithoutWaitingPost('NodeWorker.disable'); + sessionWithoutWaiting.disconnect(); +} + async function test() { const session = new Session(); session.connect(); @@ -219,6 +265,7 @@ async function test() { await testNoWaitOnStart(session, post); await testTwoWorkers(session, post); + await testWaitForDisconnectInWorker(session, post); session.disconnect(); console.log('Test done');