summaryrefslogtreecommitdiff
path: root/src/inspector
diff options
context:
space:
mode:
authorEugene Ostroukhov <eostroukhov@google.com>2018-09-08 19:45:10 -0700
committerEugene Ostroukhov <eostroukhov@google.com>2018-09-18 09:01:33 -0700
commitf28c6f7eef58e7c3133bb2cd457d05b986194ba3 (patch)
treeb8e8583ff735a3b0721831af51c4f92d967849f1 /src/inspector
parentba0b4e43e442926bfb9389a42aa7393f91e6748a (diff)
downloadnode-new-f28c6f7eef58e7c3133bb2cd457d05b986194ba3.tar.gz
inspector: workers debugging
Introduce a NodeTarget inspector domain modelled after ChromeDevTools Target domain. It notifies inspector frontend attached to a main V8 isolate when workers are starting and allows passing messages to inspectors on their isolates. All inspector functionality is enabled on worker isolates. PR-URL: https://github.com/nodejs/node/pull/21364 Reviewed-By: Aleksei Koziatinskii <ak239spb@gmail.com> Reviewed-By: Jan Krems <jan.krems@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'src/inspector')
-rw-r--r--src/inspector/node_protocol.pdl55
-rw-r--r--src/inspector/node_protocol_config.json7
-rw-r--r--src/inspector/worker_agent.cc154
-rw-r--r--src/inspector/worker_agent.h39
-rw-r--r--src/inspector/worker_inspector.cc128
-rw-r--r--src/inspector/worker_inspector.h98
6 files changed, 475 insertions, 6 deletions
diff --git a/src/inspector/node_protocol.pdl b/src/inspector/node_protocol.pdl
index 27b3d814c8..9fb9f1c55f 100644
--- a/src/inspector/node_protocol.pdl
+++ b/src/inspector/node_protocol.pdl
@@ -37,3 +37,58 @@ experimental domain NodeTracing
# Signals that tracing is stopped and there is no trace buffers pending flush, all data were
# delivered via dataCollected events.
event tracingComplete
+
+# Support for sending messages to Node worker Inspector instances.
+experimental domain NodeWorker
+
+ type WorkerID extends string
+
+ # Unique identifier of attached debugging session.
+ type SessionID extends string
+
+ type WorkerInfo extends object
+ properties
+ WorkerID workerId
+ string type
+ string title
+ string url
+
+ # Sends protocol message over session with given id.
+ command sendMessageToWorker
+ parameters
+ string message
+ # Identifier of the session.
+ SessionID sessionId
+
+ # Instructs the inspector to attach to running workers. Will also attach to new workers
+ # as they start
+ command enable
+ parameters
+ # Whether to new workers should be paused until the frontend sends `Runtime.runIfWaitingForDebugger`
+ # message to run them.
+ boolean waitForDebuggerOnStart
+
+ # Detaches from all running workers and disables attaching to new workers as they are started.
+ command disable
+
+ # Issued when attached to a worker.
+ event attachedToWorker
+ parameters
+ # Identifier assigned to the session used to send/receive messages.
+ SessionID sessionId
+ WorkerInfo workerInfo
+ boolean waitingForDebugger
+
+ # Issued when detached from the worker.
+ event detachedFromWorker
+ parameters
+ # Detached session identifier.
+ SessionID sessionId
+
+ # Notifies about a new protocol message received from the session
+ # (session ID is provided in attachedToWorker notification).
+ event receivedMessageFromWorker
+ parameters
+ # Identifier of a session which sends a message.
+ SessionID sessionId
+ string message
diff --git a/src/inspector/node_protocol_config.json b/src/inspector/node_protocol_config.json
index 7cea20ae93..4ef3785606 100644
--- a/src/inspector/node_protocol_config.json
+++ b/src/inspector/node_protocol_config.json
@@ -3,12 +3,7 @@
"path": "node_protocol.json",
"package": "src/node/inspector/protocol",
"output": "node/inspector/protocol",
- "namespace": ["node", "inspector", "protocol"],
- "options": [
- {
- "domain": "NodeTracing"
- }
- ]
+ "namespace": ["node", "inspector", "protocol"]
},
"exported": {
"package": "include/inspector",
diff --git a/src/inspector/worker_agent.cc b/src/inspector/worker_agent.cc
new file mode 100644
index 0000000000..fccd6d57a5
--- /dev/null
+++ b/src/inspector/worker_agent.cc
@@ -0,0 +1,154 @@
+#include "worker_agent.h"
+
+#include "main_thread_interface.h"
+#include "worker_inspector.h"
+
+namespace node {
+namespace inspector {
+namespace protocol {
+
+class NodeWorkers
+ : public std::enable_shared_from_this<NodeWorkers> {
+ public:
+ explicit NodeWorkers(std::weak_ptr<NodeWorker::Frontend> frontend,
+ std::shared_ptr<MainThreadHandle> thread)
+ : frontend_(frontend), thread_(thread) {}
+ void WorkerCreated(const std::string& title,
+ const std::string& url,
+ bool waiting,
+ std::shared_ptr<MainThreadHandle> target);
+ void Receive(const std::string& id, const std::string& message);
+ void Send(const std::string& id, const std::string& message);
+ void Detached(const std::string& id);
+
+ private:
+ std::weak_ptr<NodeWorker::Frontend> frontend_;
+ std::shared_ptr<MainThreadHandle> thread_;
+ std::unordered_map<std::string, std::unique_ptr<InspectorSession>> sessions_;
+ int next_target_id_ = 0;
+};
+
+namespace {
+class AgentWorkerInspectorDelegate : public WorkerDelegate {
+ public:
+ explicit AgentWorkerInspectorDelegate(std::shared_ptr<NodeWorkers> workers)
+ : workers_(workers) {}
+
+ void WorkerCreated(const std::string& title,
+ const std::string& url,
+ bool waiting,
+ std::shared_ptr<MainThreadHandle> target) override {
+ workers_->WorkerCreated(title, url, waiting, target);
+ }
+
+ private:
+ std::shared_ptr<NodeWorkers> workers_;
+};
+
+class ParentInspectorSessionDelegate : public InspectorSessionDelegate {
+ public:
+ ParentInspectorSessionDelegate(const std::string& id,
+ std::shared_ptr<NodeWorkers> workers)
+ : id_(id), workers_(workers) {}
+
+ ~ParentInspectorSessionDelegate() override {
+ workers_->Detached(id_);
+ }
+
+ void SendMessageToFrontend(const v8_inspector::StringView& msg) override {
+ std::string message = protocol::StringUtil::StringViewToUtf8(msg);
+ workers_->Send(id_, message);
+ }
+
+ private:
+ std::string id_;
+ std::shared_ptr<NodeWorkers> workers_;
+};
+
+std::unique_ptr<NodeWorker::WorkerInfo> WorkerInfo(const std::string& id,
+ const std::string& title,
+ const std::string& url) {
+ return NodeWorker::WorkerInfo::create()
+ .setWorkerId(id)
+ .setTitle(title)
+ .setUrl(url)
+ .setType("worker").build();
+}
+} // namespace
+
+WorkerAgent::WorkerAgent(std::weak_ptr<WorkerManager> manager)
+ : manager_(manager) {}
+
+
+void WorkerAgent::Wire(UberDispatcher* dispatcher) {
+ frontend_.reset(new NodeWorker::Frontend(dispatcher->channel()));
+ NodeWorker::Dispatcher::wire(dispatcher, this);
+ auto manager = manager_.lock();
+ CHECK_NOT_NULL(manager);
+ workers_ =
+ std::make_shared<NodeWorkers>(frontend_, manager->MainThread());
+}
+
+DispatchResponse WorkerAgent::sendMessageToWorker(const String& message,
+ const String& sessionId) {
+ workers_->Receive(sessionId, message);
+ return DispatchResponse::OK();
+}
+
+DispatchResponse WorkerAgent::enable(bool waitForDebuggerOnStart) {
+ auto manager = manager_.lock();
+ if (!manager) {
+ return DispatchResponse::OK();
+ }
+ if (!event_handle_) {
+ std::unique_ptr<AgentWorkerInspectorDelegate> delegate(
+ new AgentWorkerInspectorDelegate(workers_));
+ event_handle_ = manager->SetAutoAttach(std::move(delegate));
+ }
+ event_handle_->SetWaitOnStart(waitForDebuggerOnStart);
+ return DispatchResponse::OK();
+}
+
+DispatchResponse WorkerAgent::disable() {
+ event_handle_.reset();
+ return DispatchResponse::OK();
+}
+
+void NodeWorkers::WorkerCreated(const std::string& title,
+ const std::string& url,
+ bool waiting,
+ std::shared_ptr<MainThreadHandle> target) {
+ auto frontend = frontend_.lock();
+ if (!frontend)
+ return;
+ std::string id = std::to_string(++next_target_id_);
+ auto delegate = thread_->MakeDelegateThreadSafe(
+ std::unique_ptr<InspectorSessionDelegate>(
+ new ParentInspectorSessionDelegate(id, shared_from_this())));
+ sessions_[id] = target->Connect(std::move(delegate), true);
+ frontend->attachedToWorker(id, WorkerInfo(id, title, url), waiting);
+}
+
+void NodeWorkers::Send(const std::string& id, const std::string& message) {
+ auto frontend = frontend_.lock();
+ if (frontend)
+ frontend->receivedMessageFromWorker(id, message);
+}
+
+void NodeWorkers::Receive(const std::string& id, const std::string& message) {
+ auto it = sessions_.find(id);
+ if (it != sessions_.end())
+ it->second->Dispatch(Utf8ToStringView(message)->string());
+}
+
+void NodeWorkers::Detached(const std::string& id) {
+ if (sessions_.erase(id) == 0)
+ return;
+ auto frontend = frontend_.lock();
+ if (frontend) {
+ frontend->detachedFromWorker(id);
+ }
+}
+} // namespace protocol
+} // namespace inspector
+} // namespace node
diff --git a/src/inspector/worker_agent.h b/src/inspector/worker_agent.h
new file mode 100644
index 0000000000..402c719416
--- /dev/null
+++ b/src/inspector/worker_agent.h
@@ -0,0 +1,39 @@
+#ifndef SRC_INSPECTOR_WORKER_AGENT_H_
+#define SRC_INSPECTOR_WORKER_AGENT_H_
+
+#include "node/inspector/protocol/NodeWorker.h"
+#include "v8.h"
+
+
+namespace node {
+namespace inspector {
+class WorkerManagerEventHandle;
+class WorkerManager;
+
+namespace protocol {
+class NodeWorkers;
+
+class WorkerAgent : public NodeWorker::Backend {
+ public:
+ explicit WorkerAgent(std::weak_ptr<WorkerManager> manager);
+ ~WorkerAgent() override = default;
+
+ void Wire(UberDispatcher* dispatcher);
+
+ DispatchResponse sendMessageToWorker(const String& message,
+ const String& sessionId) override;
+
+ DispatchResponse enable(bool waitForDebuggerOnStart) override;
+ DispatchResponse disable() override;
+
+ private:
+ std::shared_ptr<NodeWorker::Frontend> frontend_;
+ std::weak_ptr<WorkerManager> manager_;
+ std::unique_ptr<WorkerManagerEventHandle> event_handle_;
+ std::shared_ptr<NodeWorkers> workers_;
+};
+} // namespace protocol
+} // namespace inspector
+} // namespace node
+
+#endif // SRC_INSPECTOR_WORKER_AGENT_H_
diff --git a/src/inspector/worker_inspector.cc b/src/inspector/worker_inspector.cc
new file mode 100644
index 0000000000..52e71a562d
--- /dev/null
+++ b/src/inspector/worker_inspector.cc
@@ -0,0 +1,128 @@
+#include "worker_inspector.h"
+
+#include "main_thread_interface.h"
+
+namespace node {
+namespace inspector {
+namespace {
+
+class WorkerStartedRequest : public Request {
+ public:
+ WorkerStartedRequest(
+ int id,
+ const std::string& url,
+ std::shared_ptr<node::inspector::MainThreadHandle> worker_thread,
+ bool waiting)
+ : id_(id),
+ info_(BuildWorkerTitle(id), url, worker_thread),
+ waiting_(waiting) {}
+ void Call(MainThreadInterface* thread) override {
+ auto manager = thread->inspector_agent()->GetWorkerManager();
+ manager->WorkerStarted(id_, info_, waiting_);
+ }
+
+ private:
+ static std::string BuildWorkerTitle(int id) {
+ return "Worker " + std::to_string(id);
+ }
+
+ int id_;
+ WorkerInfo info_;
+ bool waiting_;
+};
+
+
+void Report(const std::unique_ptr<WorkerDelegate>& delegate,
+ const WorkerInfo& info, bool waiting) {
+ if (info.worker_thread)
+ delegate->WorkerCreated(info.title, info.url, waiting, info.worker_thread);
+}
+
+class WorkerFinishedRequest : public Request {
+ public:
+ explicit WorkerFinishedRequest(int worker_id) : worker_id_(worker_id) {}
+
+ void Call(MainThreadInterface* thread) override {
+ thread->inspector_agent()->GetWorkerManager()->WorkerFinished(worker_id_);
+ }
+
+ private:
+ int worker_id_;
+};
+} // namespace
+
+
+ParentInspectorHandle::ParentInspectorHandle(
+ int id, const std::string& url,
+ std::shared_ptr<MainThreadHandle> parent_thread, bool wait_for_connect)
+ : id_(id), url_(url), parent_thread_(parent_thread),
+ wait_(wait_for_connect) {}
+
+ParentInspectorHandle::~ParentInspectorHandle() {
+ parent_thread_->Post(
+ std::unique_ptr<Request>(new WorkerFinishedRequest(id_)));
+}
+
+void ParentInspectorHandle::WorkerStarted(
+ std::shared_ptr<MainThreadHandle> worker_thread, bool waiting) {
+ std::unique_ptr<Request> request(
+ new WorkerStartedRequest(id_, url_, worker_thread, waiting));
+ parent_thread_->Post(std::move(request));
+}
+
+void WorkerManager::WorkerFinished(int session_id) {
+ children_.erase(session_id);
+}
+
+void WorkerManager::WorkerStarted(int session_id,
+ const WorkerInfo& info,
+ bool waiting) {
+ if (info.worker_thread->Expired())
+ return;
+ children_.emplace(session_id, info);
+ for (const auto& delegate : delegates_) {
+ Report(delegate.second, info, waiting);
+ }
+}
+
+std::unique_ptr<ParentInspectorHandle>
+WorkerManager::NewParentHandle(int thread_id, const std::string& url) {
+ bool wait = !delegates_waiting_on_start_.empty();
+ return std::unique_ptr<ParentInspectorHandle>(
+ new ParentInspectorHandle(thread_id, url, thread_, wait));
+}
+
+void WorkerManager::RemoveAttachDelegate(int id) {
+ delegates_.erase(id);
+ delegates_waiting_on_start_.erase(id);
+}
+
+std::unique_ptr<WorkerManagerEventHandle> WorkerManager::SetAutoAttach(
+ std::unique_ptr<WorkerDelegate> attach_delegate) {
+ int id = ++next_delegate_id_;
+ delegates_[id] = std::move(attach_delegate);
+ const auto& delegate = delegates_[id];
+ for (const auto& worker : children_) {
+ // Waiting is only reported when a worker is started, same as browser
+ Report(delegate, worker.second, false);
+ }
+ return std::unique_ptr<WorkerManagerEventHandle>(
+ new WorkerManagerEventHandle(shared_from_this(), id));
+}
+
+void WorkerManager::SetWaitOnStartForDelegate(int id, bool wait) {
+ if (wait)
+ delegates_waiting_on_start_.insert(id);
+ else
+ delegates_waiting_on_start_.erase(id);
+}
+
+void WorkerManagerEventHandle::SetWaitOnStart(bool wait_on_start) {
+ manager_->SetWaitOnStartForDelegate(id_, wait_on_start);
+}
+
+WorkerManagerEventHandle::~WorkerManagerEventHandle() {
+ manager_->RemoveAttachDelegate(id_);
+}
+} // namespace inspector
+} // namespace node
diff --git a/src/inspector/worker_inspector.h b/src/inspector/worker_inspector.h
new file mode 100644
index 0000000000..e3c96cf62f
--- /dev/null
+++ b/src/inspector/worker_inspector.h
@@ -0,0 +1,98 @@
+#ifndef SRC_INSPECTOR_WORKER_INSPECTOR_H_
+#define SRC_INSPECTOR_WORKER_INSPECTOR_H_
+
+#if !HAVE_INSPECTOR
+#error("This header can only be used when inspector is enabled")
+#endif
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace node {
+namespace inspector {
+class MainThreadHandle;
+class WorkerManager;
+
+class WorkerDelegate {
+ public:
+ virtual void WorkerCreated(const std::string& title,
+ const std::string& url,
+ bool waiting,
+ std::shared_ptr<MainThreadHandle> worker) = 0;
+};
+
+class WorkerManagerEventHandle {
+ public:
+ explicit WorkerManagerEventHandle(std::shared_ptr<WorkerManager> manager,
+ int id)
+ : manager_(manager), id_(id) {}
+ void SetWaitOnStart(bool wait_on_start);
+ ~WorkerManagerEventHandle();
+
+ private:
+ std::shared_ptr<WorkerManager> manager_;
+ int id_;
+};
+
+struct WorkerInfo {
+ WorkerInfo(const std::string& target_title,
+ const std::string& target_url,
+ std::shared_ptr<MainThreadHandle> worker_thread)
+ : title(target_title),
+ url(target_url),
+ worker_thread(worker_thread) {}
+ std::string title;
+ std::string url;
+ std::shared_ptr<MainThreadHandle> worker_thread;
+};
+
+class ParentInspectorHandle {
+ public:
+ ParentInspectorHandle(int id, const std::string& url,
+ std::shared_ptr<MainThreadHandle> parent_thread,
+ bool wait_for_connect);
+ ~ParentInspectorHandle();
+ void WorkerStarted(std::shared_ptr<MainThreadHandle> worker_thread,
+ bool waiting);
+ bool WaitForConnect() {
+ return wait_;
+ }
+
+ private:
+ int id_;
+ std::string url_;
+ std::shared_ptr<MainThreadHandle> parent_thread_;
+ bool wait_;
+};
+
+class WorkerManager : public std::enable_shared_from_this<WorkerManager> {
+ public:
+ explicit WorkerManager(std::shared_ptr<MainThreadHandle> thread)
+ : thread_(thread) {}
+
+ std::unique_ptr<ParentInspectorHandle> NewParentHandle(
+ int thread_id, const std::string& url);
+ void WorkerStarted(int session_id, const WorkerInfo& info, bool waiting);
+ void WorkerFinished(int session_id);
+ std::unique_ptr<WorkerManagerEventHandle> SetAutoAttach(
+ std::unique_ptr<WorkerDelegate> attach_delegate);
+ void SetWaitOnStartForDelegate(int id, bool wait);
+ void RemoveAttachDelegate(int id);
+ std::shared_ptr<MainThreadHandle> MainThread() {
+ return thread_;
+ }
+
+ private:
+ std::shared_ptr<MainThreadHandle> thread_;
+ std::unordered_map<int, WorkerInfo> children_;
+ std::unordered_map<int, std::unique_ptr<WorkerDelegate>> delegates_;
+ // If any one needs it, workers stop for all
+ std::unordered_set<int> delegates_waiting_on_start_;
+ int next_delegate_id_ = 0;
+};
+} // namespace inspector
+} // namespace node
+
+#endif // SRC_INSPECTOR_WORKER_INSPECTOR_H_