diff options
author | Eugene Ostroukhov <eostroukhov@google.com> | 2018-09-08 19:45:10 -0700 |
---|---|---|
committer | Eugene Ostroukhov <eostroukhov@google.com> | 2018-09-18 09:01:33 -0700 |
commit | f28c6f7eef58e7c3133bb2cd457d05b986194ba3 (patch) | |
tree | b8e8583ff735a3b0721831af51c4f92d967849f1 /src/inspector | |
parent | ba0b4e43e442926bfb9389a42aa7393f91e6748a (diff) | |
download | node-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.pdl | 55 | ||||
-rw-r--r-- | src/inspector/node_protocol_config.json | 7 | ||||
-rw-r--r-- | src/inspector/worker_agent.cc | 154 | ||||
-rw-r--r-- | src/inspector/worker_agent.h | 39 | ||||
-rw-r--r-- | src/inspector/worker_inspector.cc | 128 | ||||
-rw-r--r-- | src/inspector/worker_inspector.h | 98 |
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_ |