xref: /third_party/node/src/inspector_agent.cc (revision 1cb0ef41)
1#include "inspector_agent.h"
2
3#include "env-inl.h"
4#include "inspector/main_thread_interface.h"
5#include "inspector/node_string.h"
6#include "inspector/runtime_agent.h"
7#include "inspector/tracing_agent.h"
8#include "inspector/worker_agent.h"
9#include "inspector/worker_inspector.h"
10#include "inspector_io.h"
11#include "node/inspector/protocol/Protocol.h"
12#include "node_errors.h"
13#include "node_internals.h"
14#include "node_options-inl.h"
15#include "node_process-inl.h"
16#include "node_url.h"
17#include "util-inl.h"
18#include "timer_wrap-inl.h"
19#include "v8-inspector.h"
20#include "v8-platform.h"
21
22#include "libplatform/libplatform.h"
23
24#ifdef __POSIX__
25#include <pthread.h>
26#include <climits>  // PTHREAD_STACK_MIN
27#endif  // __POSIX__
28
29#include <algorithm>
30#include <cstring>
31#include <sstream>
32#include <unordered_map>
33#include <vector>
34
35namespace node {
36namespace inspector {
37namespace {
38
39using node::OnFatalError;
40
41using v8::Context;
42using v8::Function;
43using v8::HandleScope;
44using v8::Isolate;
45using v8::Local;
46using v8::Message;
47using v8::Object;
48using v8::Value;
49
50using v8_inspector::StringBuffer;
51using v8_inspector::StringView;
52using v8_inspector::V8Inspector;
53using v8_inspector::V8InspectorClient;
54
55#ifdef __POSIX__
56static uv_sem_t start_io_thread_semaphore;
57#endif  // __POSIX__
58static uv_async_t start_io_thread_async;
59// This is just an additional check to make sure start_io_thread_async
60// is not accidentally re-used or used when uninitialized.
61static std::atomic_bool start_io_thread_async_initialized { false };
62// Protects the Agent* stored in start_io_thread_async.data.
63static Mutex start_io_thread_async_mutex;
64
65std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
66                                               Local<Value> value) {
67  TwoByteValue buffer(isolate, value);
68  return StringBuffer::create(StringView(*buffer, buffer.length()));
69}
70
71// Called on the main thread.
72void StartIoThreadAsyncCallback(uv_async_t* handle) {
73  static_cast<Agent*>(handle->data)->StartIoThread();
74}
75
76
77#ifdef __POSIX__
78static void StartIoThreadWakeup(int signo, siginfo_t* info, void* ucontext) {
79  uv_sem_post(&start_io_thread_semaphore);
80}
81
82inline void* StartIoThreadMain(void* unused) {
83  for (;;) {
84    uv_sem_wait(&start_io_thread_semaphore);
85    Mutex::ScopedLock lock(start_io_thread_async_mutex);
86
87    CHECK(start_io_thread_async_initialized);
88    Agent* agent = static_cast<Agent*>(start_io_thread_async.data);
89    if (agent != nullptr)
90      agent->RequestIoThreadStart();
91  }
92}
93
94static int StartDebugSignalHandler() {
95  // Start a watchdog thread for calling v8::Debug::DebugBreak() because
96  // it's not safe to call directly from the signal handler, it can
97  // deadlock with the thread it interrupts.
98  CHECK_EQ(0, uv_sem_init(&start_io_thread_semaphore, 0));
99  pthread_attr_t attr;
100  CHECK_EQ(0, pthread_attr_init(&attr));
101#if defined(PTHREAD_STACK_MIN) && !defined(__FreeBSD__)
102  // PTHREAD_STACK_MIN is 2 KiB with musl libc, which is too small to safely
103  // receive signals. PTHREAD_STACK_MIN + MINSIGSTKSZ is 8 KiB on arm64, which
104  // is the musl architecture with the biggest MINSIGSTKSZ so let's use that
105  // as a lower bound and let's quadruple it just in case. The goal is to avoid
106  // creating a big 2 or 4 MiB address space gap (problematic on 32 bits
107  // because of fragmentation), not squeeze out every last byte.
108  // Omitted on FreeBSD because it doesn't seem to like small stacks.
109  const size_t stack_size = std::max(static_cast<size_t>(4 * 8192),
110                                     static_cast<size_t>(PTHREAD_STACK_MIN));
111  CHECK_EQ(0, pthread_attr_setstacksize(&attr, stack_size));
112#endif  // defined(PTHREAD_STACK_MIN) && !defined(__FreeBSD__)
113  CHECK_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
114  sigset_t sigmask;
115  // Mask all signals.
116  sigfillset(&sigmask);
117  sigset_t savemask;
118  CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &savemask));
119  sigmask = savemask;
120  pthread_t thread;
121  const int err = pthread_create(&thread, &attr,
122                                 StartIoThreadMain, nullptr);
123  // Restore original mask
124  CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
125  CHECK_EQ(0, pthread_attr_destroy(&attr));
126  if (err != 0) {
127    fprintf(stderr, "node[%u]: pthread_create: %s\n",
128            uv_os_getpid(), strerror(err));
129    fflush(stderr);
130    // Leave SIGUSR1 blocked.  We don't install a signal handler,
131    // receiving the signal would terminate the process.
132    return -err;
133  }
134  RegisterSignalHandler(SIGUSR1, StartIoThreadWakeup);
135  // Unblock SIGUSR1.  A pending SIGUSR1 signal will now be delivered.
136  sigemptyset(&sigmask);
137  sigaddset(&sigmask, SIGUSR1);
138  CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sigmask, nullptr));
139  return 0;
140}
141#endif  // __POSIX__
142
143
144#ifdef _WIN32
145DWORD WINAPI StartIoThreadProc(void* arg) {
146  Mutex::ScopedLock lock(start_io_thread_async_mutex);
147  CHECK(start_io_thread_async_initialized);
148  Agent* agent = static_cast<Agent*>(start_io_thread_async.data);
149  if (agent != nullptr)
150    agent->RequestIoThreadStart();
151  return 0;
152}
153
154static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf,
155                                            size_t buf_len) {
156  return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid);
157}
158
159static int StartDebugSignalHandler() {
160  wchar_t mapping_name[32];
161  HANDLE mapping_handle;
162  DWORD pid;
163  LPTHREAD_START_ROUTINE* handler;
164
165  pid = uv_os_getpid();
166
167  if (GetDebugSignalHandlerMappingName(pid,
168                                       mapping_name,
169                                       arraysize(mapping_name)) < 0) {
170    return -1;
171  }
172
173  mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE,
174                                      nullptr,
175                                      PAGE_READWRITE,
176                                      0,
177                                      sizeof *handler,
178                                      mapping_name);
179  if (mapping_handle == nullptr) {
180    return -1;
181  }
182
183  handler = reinterpret_cast<LPTHREAD_START_ROUTINE*>(
184      MapViewOfFile(mapping_handle,
185                    FILE_MAP_ALL_ACCESS,
186                    0,
187                    0,
188                    sizeof *handler));
189  if (handler == nullptr) {
190    CloseHandle(mapping_handle);
191    return -1;
192  }
193
194  *handler = StartIoThreadProc;
195
196  UnmapViewOfFile(static_cast<void*>(handler));
197
198  return 0;
199}
200#endif  // _WIN32
201
202
203const int CONTEXT_GROUP_ID = 1;
204
205std::string GetWorkerLabel(node::Environment* env) {
206  std::ostringstream result;
207  result << "Worker[" << env->thread_id() << "]";
208  return result.str();
209}
210
211class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
212                          public protocol::FrontendChannel {
213 public:
214  explicit ChannelImpl(Environment* env,
215                       const std::unique_ptr<V8Inspector>& inspector,
216                       std::shared_ptr<WorkerManager> worker_manager,
217                       std::unique_ptr<InspectorSessionDelegate> delegate,
218                       std::shared_ptr<MainThreadHandle> main_thread_,
219                       bool prevent_shutdown)
220      : delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown),
221        retaining_context_(false) {
222    session_ = inspector->connect(CONTEXT_GROUP_ID, this, StringView(),
223                                  V8Inspector::ClientTrustLevel::kFullyTrusted);
224    node_dispatcher_ = std::make_unique<protocol::UberDispatcher>(this);
225    tracing_agent_ =
226        std::make_unique<protocol::TracingAgent>(env, main_thread_);
227    tracing_agent_->Wire(node_dispatcher_.get());
228    if (worker_manager) {
229      worker_agent_ = std::make_unique<protocol::WorkerAgent>(worker_manager);
230      worker_agent_->Wire(node_dispatcher_.get());
231    }
232    runtime_agent_ = std::make_unique<protocol::RuntimeAgent>();
233    runtime_agent_->Wire(node_dispatcher_.get());
234  }
235
236  ~ChannelImpl() override {
237    tracing_agent_->disable();
238    tracing_agent_.reset();  // Dispose before the dispatchers
239    if (worker_agent_) {
240      worker_agent_->disable();
241      worker_agent_.reset();  // Dispose before the dispatchers
242    }
243    runtime_agent_->disable();
244    runtime_agent_.reset();  // Dispose before the dispatchers
245  }
246
247  void dispatchProtocolMessage(const StringView& message) {
248    std::string raw_message = protocol::StringUtil::StringViewToUtf8(message);
249    per_process::Debug(DebugCategory::INSPECTOR_SERVER,
250                       "[inspector received] %s\n",
251                       raw_message);
252    std::unique_ptr<protocol::DictionaryValue> value =
253        protocol::DictionaryValue::cast(protocol::StringUtil::parseMessage(
254            raw_message, false));
255    int call_id;
256    std::string method;
257    node_dispatcher_->parseCommand(value.get(), &call_id, &method);
258    if (v8_inspector::V8InspectorSession::canDispatchMethod(
259            Utf8ToStringView(method)->string())) {
260      session_->dispatchProtocolMessage(message);
261    } else {
262      node_dispatcher_->dispatch(call_id, method, std::move(value),
263                                 raw_message);
264    }
265  }
266
267  void schedulePauseOnNextStatement(const std::string& reason) {
268    std::unique_ptr<StringBuffer> buffer = Utf8ToStringView(reason);
269    session_->schedulePauseOnNextStatement(buffer->string(), buffer->string());
270  }
271
272  bool preventShutdown() {
273    return prevent_shutdown_;
274  }
275
276  bool notifyWaitingForDisconnect() {
277    retaining_context_ = runtime_agent_->notifyWaitingForDisconnect();
278    return retaining_context_;
279  }
280
281  bool retainingContext() {
282    return retaining_context_;
283  }
284
285 private:
286  void sendResponse(
287      int callId,
288      std::unique_ptr<v8_inspector::StringBuffer> message) override {
289    sendMessageToFrontend(message->string());
290  }
291
292  void sendNotification(
293      std::unique_ptr<v8_inspector::StringBuffer> message) override {
294    sendMessageToFrontend(message->string());
295  }
296
297  void flushProtocolNotifications() override { }
298
299  void sendMessageToFrontend(const StringView& message) {
300    if (per_process::enabled_debug_list.enabled(
301            DebugCategory::INSPECTOR_SERVER)) {
302      std::string raw_message = protocol::StringUtil::StringViewToUtf8(message);
303      per_process::Debug(DebugCategory::INSPECTOR_SERVER,
304                         "[inspector send] %s\n",
305                         raw_message);
306    }
307    delegate_->SendMessageToFrontend(message);
308  }
309
310  void sendMessageToFrontend(const std::string& message) {
311    sendMessageToFrontend(Utf8ToStringView(message)->string());
312  }
313
314  using Serializable = protocol::Serializable;
315
316  void sendProtocolResponse(int callId,
317                            std::unique_ptr<Serializable> message) override {
318    sendMessageToFrontend(message->serializeToJSON());
319  }
320  void sendProtocolNotification(
321      std::unique_ptr<Serializable> message) override {
322    sendMessageToFrontend(message->serializeToJSON());
323  }
324
325  void fallThrough(int callId,
326                   const std::string& method,
327                   const std::string& message) override {
328    DCHECK(false);
329  }
330
331  std::unique_ptr<protocol::RuntimeAgent> runtime_agent_;
332  std::unique_ptr<protocol::TracingAgent> tracing_agent_;
333  std::unique_ptr<protocol::WorkerAgent> worker_agent_;
334  std::unique_ptr<InspectorSessionDelegate> delegate_;
335  std::unique_ptr<v8_inspector::V8InspectorSession> session_;
336  std::unique_ptr<protocol::UberDispatcher> node_dispatcher_;
337  bool prevent_shutdown_;
338  bool retaining_context_;
339};
340
341class SameThreadInspectorSession : public InspectorSession {
342 public:
343  SameThreadInspectorSession(
344      int session_id, std::shared_ptr<NodeInspectorClient> client)
345      : session_id_(session_id), client_(client) {}
346  ~SameThreadInspectorSession() override;
347  void Dispatch(const v8_inspector::StringView& message) override;
348
349 private:
350  int session_id_;
351  std::weak_ptr<NodeInspectorClient> client_;
352};
353
354void NotifyClusterWorkersDebugEnabled(Environment* env) {
355  Isolate* isolate = env->isolate();
356  HandleScope handle_scope(isolate);
357  Local<Context> context = env->context();
358
359  // Send message to enable debug in cluster workers
360  Local<Object> message = Object::New(isolate);
361  message->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cmd"),
362               FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")).Check();
363  ProcessEmit(env, "internalMessage", message);
364}
365
366#ifdef _WIN32
367bool IsFilePath(const std::string& path) {
368  // '\\'
369  if (path.length() > 2 && path[0] == '\\' && path[1] == '\\')
370    return true;
371  // '[A-Z]:[/\\]'
372  if (path.length() < 3)
373    return false;
374  if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
375    return path[1] == ':' && (path[2] == '/' || path[2] == '\\');
376  return false;
377}
378#else
379bool IsFilePath(const std::string& path) {
380  return !path.empty() && path[0] == '/';
381}
382#endif  // __POSIX__
383
384void ThrowUninitializedInspectorError(Environment* env) {
385  HandleScope scope(env->isolate());
386
387  const char* msg = "This Environment was initialized without a V8::Inspector";
388  Local<Value> exception =
389    v8::String::NewFromUtf8(env->isolate(), msg).ToLocalChecked();
390
391  env->isolate()->ThrowException(exception);
392}
393
394}  // namespace
395
396class NodeInspectorClient : public V8InspectorClient {
397 public:
398  explicit NodeInspectorClient(node::Environment* env, bool is_main)
399      : env_(env), is_main_(is_main) {
400    client_ = V8Inspector::create(env->isolate(), this);
401    // TODO(bnoordhuis) Make name configurable from src/node.cc.
402    std::string name =
403        is_main_ ? GetHumanReadableProcessName() : GetWorkerLabel(env);
404    ContextInfo info(name);
405    info.is_default = true;
406    contextCreated(env->context(), info);
407  }
408
409  void runMessageLoopOnPause(int context_group_id) override {
410    waiting_for_resume_ = true;
411    runMessageLoop();
412  }
413
414  void waitForSessionsDisconnect() {
415    waiting_for_sessions_disconnect_ = true;
416    runMessageLoop();
417  }
418
419  void waitForFrontend() {
420    waiting_for_frontend_ = true;
421    runMessageLoop();
422  }
423
424  void maxAsyncCallStackDepthChanged(int depth) override {
425    if (waiting_for_sessions_disconnect_) {
426      // V8 isolate is mostly done and is only letting Inspector protocol
427      // clients gather data.
428      return;
429    }
430    if (auto agent = env_->inspector_agent()) {
431      if (depth == 0) {
432        agent->DisableAsyncHook();
433      } else {
434        agent->EnableAsyncHook();
435      }
436    }
437  }
438
439  void contextCreated(Local<Context> context, const ContextInfo& info) {
440    auto name_buffer = Utf8ToStringView(info.name);
441    auto origin_buffer = Utf8ToStringView(info.origin);
442    std::unique_ptr<StringBuffer> aux_data_buffer;
443
444    v8_inspector::V8ContextInfo v8info(
445        context, CONTEXT_GROUP_ID, name_buffer->string());
446    v8info.origin = origin_buffer->string();
447
448    if (info.is_default) {
449      aux_data_buffer = Utf8ToStringView("{\"isDefault\":true}");
450    } else {
451      aux_data_buffer = Utf8ToStringView("{\"isDefault\":false}");
452    }
453    v8info.auxData = aux_data_buffer->string();
454
455    client_->contextCreated(v8info);
456  }
457
458  void contextDestroyed(Local<Context> context) {
459    client_->contextDestroyed(context);
460  }
461
462  void quitMessageLoopOnPause() override {
463    waiting_for_resume_ = false;
464  }
465
466  void runIfWaitingForDebugger(int context_group_id) override {
467    waiting_for_frontend_ = false;
468  }
469
470  int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,
471                      bool prevent_shutdown) {
472    int session_id = next_session_id_++;
473    channels_[session_id] = std::make_unique<ChannelImpl>(env_,
474                                                          client_,
475                                                          getWorkerManager(),
476                                                          std::move(delegate),
477                                                          getThreadHandle(),
478                                                          prevent_shutdown);
479    return session_id;
480  }
481
482  void disconnectFrontend(int session_id) {
483    auto it = channels_.find(session_id);
484    if (it == channels_.end())
485      return;
486    bool retaining_context = it->second->retainingContext();
487    channels_.erase(it);
488    if (retaining_context) {
489      for (const auto& id_channel : channels_) {
490        if (id_channel.second->retainingContext())
491          return;
492      }
493      contextDestroyed(env_->context());
494    }
495    if (waiting_for_sessions_disconnect_ && !is_main_)
496      waiting_for_sessions_disconnect_ = false;
497  }
498
499  void dispatchMessageFromFrontend(int session_id, const StringView& message) {
500    channels_[session_id]->dispatchProtocolMessage(message);
501  }
502
503  Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
504    return env_->context();
505  }
506
507  void installAdditionalCommandLineAPI(Local<Context> context,
508                                       Local<Object> target) override {
509    Local<Function> installer = env_->inspector_console_extension_installer();
510    if (!installer.IsEmpty()) {
511      Local<Value> argv[] = {target};
512      // If there is an exception, proceed in JS land
513      USE(installer->Call(context, target, arraysize(argv), argv));
514    }
515  }
516
517  void ReportUncaughtException(Local<Value> error, Local<Message> message) {
518    Isolate* isolate = env_->isolate();
519    Local<Context> context = env_->context();
520
521    int script_id = message->GetScriptOrigin().ScriptId();
522
523    Local<v8::StackTrace> stack_trace = message->GetStackTrace();
524
525    if (!stack_trace.IsEmpty() && stack_trace->GetFrameCount() > 0 &&
526        script_id == stack_trace->GetFrame(isolate, 0)->GetScriptId()) {
527      script_id = 0;
528    }
529
530    const uint8_t DETAILS[] = "Uncaught";
531
532    client_->exceptionThrown(
533        context,
534        StringView(DETAILS, sizeof(DETAILS) - 1),
535        error,
536        ToProtocolString(isolate, message->Get())->string(),
537        ToProtocolString(isolate, message->GetScriptResourceName())->string(),
538        message->GetLineNumber(context).FromMaybe(0),
539        message->GetStartColumn(context).FromMaybe(0),
540        client_->createStackTrace(stack_trace),
541        script_id);
542  }
543
544  void startRepeatingTimer(double interval_s,
545                           TimerCallback callback,
546                           void* data) override {
547    auto result =
548        timers_.emplace(std::piecewise_construct, std::make_tuple(data),
549                        std::make_tuple(env_, [=]() { callback(data); }));
550    CHECK(result.second);
551    uint64_t interval = static_cast<uint64_t>(1000 * interval_s);
552    result.first->second.Update(interval, interval);
553  }
554
555  void cancelTimer(void* data) override {
556    timers_.erase(data);
557  }
558
559  // Async stack traces instrumentation.
560  void AsyncTaskScheduled(const StringView& task_name, void* task,
561                          bool recurring) {
562    client_->asyncTaskScheduled(task_name, task, recurring);
563  }
564
565  void AsyncTaskCanceled(void* task) {
566    client_->asyncTaskCanceled(task);
567  }
568
569  void AsyncTaskStarted(void* task) {
570    client_->asyncTaskStarted(task);
571  }
572
573  void AsyncTaskFinished(void* task) {
574    client_->asyncTaskFinished(task);
575  }
576
577  void AllAsyncTasksCanceled() {
578    client_->allAsyncTasksCanceled();
579  }
580
581  void schedulePauseOnNextStatement(const std::string& reason) {
582    for (const auto& id_channel : channels_) {
583      id_channel.second->schedulePauseOnNextStatement(reason);
584    }
585  }
586
587  bool hasConnectedSessions() {
588    for (const auto& id_channel : channels_) {
589      // Other sessions are "invisible" more most purposes
590      if (id_channel.second->preventShutdown())
591        return true;
592    }
593    return false;
594  }
595
596  bool notifyWaitingForDisconnect() {
597    bool retaining_context = false;
598    for (const auto& id_channel : channels_) {
599      if (id_channel.second->notifyWaitingForDisconnect())
600        retaining_context = true;
601    }
602    return retaining_context;
603  }
604
605  std::shared_ptr<MainThreadHandle> getThreadHandle() {
606    if (!interface_) {
607      interface_ = std::make_shared<MainThreadInterface>(
608          env_->inspector_agent());
609    }
610    return interface_->GetHandle();
611  }
612
613  std::shared_ptr<WorkerManager> getWorkerManager() {
614    if (!is_main_) {
615      return nullptr;
616    }
617    if (worker_manager_ == nullptr) {
618      worker_manager_ =
619          std::make_shared<WorkerManager>(getThreadHandle());
620    }
621    return worker_manager_;
622  }
623
624  bool IsActive() {
625    return !channels_.empty();
626  }
627
628 private:
629  bool shouldRunMessageLoop() {
630    if (waiting_for_frontend_)
631      return true;
632    if (waiting_for_sessions_disconnect_ || waiting_for_resume_) {
633      return hasConnectedSessions();
634    }
635    return false;
636  }
637
638  void runMessageLoop() {
639    if (running_nested_loop_)
640      return;
641
642    running_nested_loop_ = true;
643
644    while (shouldRunMessageLoop()) {
645      if (interface_) interface_->WaitForFrontendEvent();
646      env_->RunAndClearInterrupts();
647    }
648    running_nested_loop_ = false;
649  }
650
651  double currentTimeMS() override {
652    return env_->isolate_data()->platform()->CurrentClockTimeMillis();
653  }
654
655  std::unique_ptr<StringBuffer> resourceNameToUrl(
656      const StringView& resource_name_view) override {
657    std::string resource_name =
658        protocol::StringUtil::StringViewToUtf8(resource_name_view);
659    if (!IsFilePath(resource_name))
660      return nullptr;
661
662    std::string url = node::url::FromFilePath(resource_name);
663    return Utf8ToStringView(url);
664  }
665
666  node::Environment* env_;
667  bool is_main_;
668  bool running_nested_loop_ = false;
669  std::unique_ptr<V8Inspector> client_;
670  // Note: ~ChannelImpl may access timers_ so timers_ has to come first.
671  std::unordered_map<void*, TimerWrapHandle> timers_;
672  std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
673  int next_session_id_ = 1;
674  bool waiting_for_resume_ = false;
675  bool waiting_for_frontend_ = false;
676  bool waiting_for_sessions_disconnect_ = false;
677  // Allows accessing Inspector from non-main threads
678  std::shared_ptr<MainThreadInterface> interface_;
679  std::shared_ptr<WorkerManager> worker_manager_;
680};
681
682Agent::Agent(Environment* env)
683    : parent_env_(env),
684      debug_options_(env->options()->debug_options()),
685      host_port_(env->inspector_host_port()) {}
686
687Agent::~Agent() {}
688
689bool Agent::Start(const std::string& path,
690                  const DebugOptions& options,
691                  std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
692                  bool is_main) {
693  path_ = path;
694  debug_options_ = options;
695  CHECK_NOT_NULL(host_port);
696  host_port_ = host_port;
697
698  client_ = std::make_shared<NodeInspectorClient>(parent_env_, is_main);
699  if (parent_env_->owns_inspector()) {
700    Mutex::ScopedLock lock(start_io_thread_async_mutex);
701    CHECK_EQ(start_io_thread_async_initialized.exchange(true), false);
702    CHECK_EQ(0, uv_async_init(parent_env_->event_loop(),
703                              &start_io_thread_async,
704                              StartIoThreadAsyncCallback));
705    uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async));
706    start_io_thread_async.data = this;
707    // Ignore failure, SIGUSR1 won't work, but that should not block node start.
708    StartDebugSignalHandler();
709
710    parent_env_->AddCleanupHook([](void* data) {
711      Environment* env = static_cast<Environment*>(data);
712
713      {
714        Mutex::ScopedLock lock(start_io_thread_async_mutex);
715        start_io_thread_async.data = nullptr;
716      }
717
718      // This is global, will never get freed
719      env->CloseHandle(&start_io_thread_async, [](uv_async_t*) {
720        CHECK(start_io_thread_async_initialized.exchange(false));
721      });
722    }, parent_env_);
723  }
724
725  AtExit(parent_env_, [](void* env) {
726    Agent* agent = static_cast<Environment*>(env)->inspector_agent();
727    if (agent->IsActive()) {
728      agent->WaitForDisconnect();
729    }
730  }, parent_env_);
731
732  bool wait_for_connect = options.wait_for_connect();
733  if (parent_handle_) {
734    wait_for_connect = parent_handle_->WaitForConnect();
735    parent_handle_->WorkerStarted(client_->getThreadHandle(), wait_for_connect);
736  } else if (!options.inspector_enabled || !options.allow_attaching_debugger ||
737             !StartIoThread()) {
738    return false;
739  }
740
741  // Patch the debug options to implement waitForDebuggerOnStart for
742  // the NodeWorker.enable method.
743  if (wait_for_connect) {
744    CHECK(!parent_env_->has_serialized_options());
745    debug_options_.EnableBreakFirstLine();
746    parent_env_->options()->get_debug_options()->EnableBreakFirstLine();
747    client_->waitForFrontend();
748  }
749  return true;
750}
751
752bool Agent::StartIoThread() {
753  if (io_ != nullptr)
754    return true;
755
756  if (!parent_env_->should_create_inspector() && !client_) {
757    ThrowUninitializedInspectorError(parent_env_);
758    return false;
759  }
760
761  CHECK_NOT_NULL(client_);
762
763  io_ = InspectorIo::Start(client_->getThreadHandle(),
764                           path_,
765                           host_port_,
766                           debug_options_.inspect_publish_uid);
767  if (io_ == nullptr) {
768    return false;
769  }
770  NotifyClusterWorkersDebugEnabled(parent_env_);
771  return true;
772}
773
774void Agent::Stop() {
775  io_.reset();
776}
777
778std::unique_ptr<InspectorSession> Agent::Connect(
779    std::unique_ptr<InspectorSessionDelegate> delegate,
780    bool prevent_shutdown) {
781  if (!parent_env_->should_create_inspector() && !client_) {
782    ThrowUninitializedInspectorError(parent_env_);
783    return std::unique_ptr<InspectorSession>{};
784  }
785
786  CHECK_NOT_NULL(client_);
787
788  int session_id = client_->connectFrontend(std::move(delegate),
789                                            prevent_shutdown);
790  return std::unique_ptr<InspectorSession>(
791      new SameThreadInspectorSession(session_id, client_));
792}
793
794std::unique_ptr<InspectorSession> Agent::ConnectToMainThread(
795    std::unique_ptr<InspectorSessionDelegate> delegate,
796    bool prevent_shutdown) {
797  if (!parent_env_->should_create_inspector() && !client_) {
798    ThrowUninitializedInspectorError(parent_env_);
799    return std::unique_ptr<InspectorSession>{};
800  }
801
802  CHECK_NOT_NULL(parent_handle_);
803  CHECK_NOT_NULL(client_);
804  auto thread_safe_delegate =
805      client_->getThreadHandle()->MakeDelegateThreadSafe(std::move(delegate));
806  return parent_handle_->Connect(std::move(thread_safe_delegate),
807                                 prevent_shutdown);
808}
809
810void Agent::WaitForDisconnect() {
811  if (!parent_env_->should_create_inspector() && !client_) {
812    ThrowUninitializedInspectorError(parent_env_);
813    return;
814  }
815
816  CHECK_NOT_NULL(client_);
817  bool is_worker = parent_handle_ != nullptr;
818  parent_handle_.reset();
819  if (client_->hasConnectedSessions() && !is_worker) {
820    fprintf(stderr, "Waiting for the debugger to disconnect...\n");
821    fflush(stderr);
822  }
823  if (!client_->notifyWaitingForDisconnect()) {
824    client_->contextDestroyed(parent_env_->context());
825  } else if (is_worker) {
826    client_->waitForSessionsDisconnect();
827  }
828  if (io_ != nullptr) {
829    io_->StopAcceptingNewConnections();
830    client_->waitForSessionsDisconnect();
831  }
832}
833
834void Agent::ReportUncaughtException(Local<Value> error,
835                                    Local<Message> message) {
836  if (!IsListening())
837    return;
838  client_->ReportUncaughtException(error, message);
839  WaitForDisconnect();
840}
841
842void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
843  client_->schedulePauseOnNextStatement(reason);
844}
845
846void Agent::RegisterAsyncHook(Isolate* isolate,
847                              Local<Function> enable_function,
848                              Local<Function> disable_function) {
849  parent_env_->set_inspector_enable_async_hooks(enable_function);
850  parent_env_->set_inspector_disable_async_hooks(disable_function);
851  if (pending_enable_async_hook_) {
852    CHECK(!pending_disable_async_hook_);
853    pending_enable_async_hook_ = false;
854    EnableAsyncHook();
855  } else if (pending_disable_async_hook_) {
856    CHECK(!pending_enable_async_hook_);
857    pending_disable_async_hook_ = false;
858    DisableAsyncHook();
859  }
860}
861
862void Agent::EnableAsyncHook() {
863  HandleScope scope(parent_env_->isolate());
864  Local<Function> enable = parent_env_->inspector_enable_async_hooks();
865  if (!enable.IsEmpty()) {
866    ToggleAsyncHook(parent_env_->isolate(), enable);
867  } else if (pending_disable_async_hook_) {
868    CHECK(!pending_enable_async_hook_);
869    pending_disable_async_hook_ = false;
870  } else {
871    pending_enable_async_hook_ = true;
872  }
873}
874
875void Agent::DisableAsyncHook() {
876  HandleScope scope(parent_env_->isolate());
877  Local<Function> disable = parent_env_->inspector_enable_async_hooks();
878  if (!disable.IsEmpty()) {
879    ToggleAsyncHook(parent_env_->isolate(), disable);
880  } else if (pending_enable_async_hook_) {
881    CHECK(!pending_disable_async_hook_);
882    pending_enable_async_hook_ = false;
883  } else {
884    pending_disable_async_hook_ = true;
885  }
886}
887
888void Agent::ToggleAsyncHook(Isolate* isolate, Local<Function> fn) {
889  // Guard against running this during cleanup -- no async events will be
890  // emitted anyway at that point anymore, and calling into JS is not possible.
891  // This should probably not be something we're attempting in the first place,
892  // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
893  if (!parent_env_->can_call_into_js()) return;
894  CHECK(parent_env_->has_run_bootstrapping_code());
895  HandleScope handle_scope(isolate);
896  CHECK(!fn.IsEmpty());
897  auto context = parent_env_->context();
898  v8::TryCatch try_catch(isolate);
899  USE(fn->Call(context, Undefined(isolate), 0, nullptr));
900  if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
901    PrintCaughtException(isolate, context, try_catch);
902    OnFatalError("\nnode::inspector::Agent::ToggleAsyncHook",
903                 "Cannot toggle Inspector's AsyncHook, please report this.");
904  }
905}
906
907void Agent::AsyncTaskScheduled(const StringView& task_name, void* task,
908                               bool recurring) {
909  client_->AsyncTaskScheduled(task_name, task, recurring);
910}
911
912void Agent::AsyncTaskCanceled(void* task) {
913  client_->AsyncTaskCanceled(task);
914}
915
916void Agent::AsyncTaskStarted(void* task) {
917  client_->AsyncTaskStarted(task);
918}
919
920void Agent::AsyncTaskFinished(void* task) {
921  client_->AsyncTaskFinished(task);
922}
923
924void Agent::AllAsyncTasksCanceled() {
925  client_->AllAsyncTasksCanceled();
926}
927
928void Agent::RequestIoThreadStart() {
929  // We need to attempt to interrupt V8 flow (in case Node is running
930  // continuous JS code) and to wake up libuv thread (in case Node is waiting
931  // for IO events)
932  if (!options().allow_attaching_debugger) {
933    return;
934  }
935  CHECK(start_io_thread_async_initialized);
936  uv_async_send(&start_io_thread_async);
937  parent_env_->RequestInterrupt([this](Environment*) {
938    StartIoThread();
939  });
940
941  CHECK(start_io_thread_async_initialized);
942  uv_async_send(&start_io_thread_async);
943}
944
945void Agent::ContextCreated(Local<Context> context, const ContextInfo& info) {
946  if (client_ == nullptr)  // This happens for a main context
947    return;
948  client_->contextCreated(context, info);
949}
950
951bool Agent::IsActive() {
952  if (client_ == nullptr)
953    return false;
954  return io_ != nullptr || client_->IsActive();
955}
956
957void Agent::SetParentHandle(
958    std::unique_ptr<ParentInspectorHandle> parent_handle) {
959  parent_handle_ = std::move(parent_handle);
960}
961
962std::unique_ptr<ParentInspectorHandle> Agent::GetParentHandle(
963    uint64_t thread_id, const std::string& url, const std::string& name) {
964  if (!parent_env_->should_create_inspector() && !client_) {
965    ThrowUninitializedInspectorError(parent_env_);
966    return std::unique_ptr<ParentInspectorHandle>{};
967  }
968
969  CHECK_NOT_NULL(client_);
970  if (!parent_handle_) {
971    return client_->getWorkerManager()->NewParentHandle(thread_id, url, name);
972  } else {
973    return parent_handle_->NewParentInspectorHandle(thread_id, url, name);
974  }
975}
976
977void Agent::WaitForConnect() {
978  if (!parent_env_->should_create_inspector() && !client_) {
979    ThrowUninitializedInspectorError(parent_env_);
980    return;
981  }
982
983  CHECK_NOT_NULL(client_);
984  client_->waitForFrontend();
985}
986
987std::shared_ptr<WorkerManager> Agent::GetWorkerManager() {
988  if (!parent_env_->should_create_inspector() && !client_) {
989    ThrowUninitializedInspectorError(parent_env_);
990    return std::unique_ptr<WorkerManager>{};
991  }
992
993  CHECK_NOT_NULL(client_);
994  return client_->getWorkerManager();
995}
996
997std::string Agent::GetWsUrl() const {
998  if (io_ == nullptr)
999    return "";
1000  return io_->GetWsUrl();
1001}
1002
1003SameThreadInspectorSession::~SameThreadInspectorSession() {
1004  auto client = client_.lock();
1005  if (client)
1006    client->disconnectFrontend(session_id_);
1007}
1008
1009void SameThreadInspectorSession::Dispatch(
1010    const v8_inspector::StringView& message) {
1011  auto client = client_.lock();
1012  if (client)
1013    client->dispatchMessageFromFrontend(session_id_, message);
1014}
1015
1016}  // namespace inspector
1017}  // namespace node
1018