@@ -29,7 +29,10 @@ using v8::Function;
2929using v8::HandleScope;
3030using v8::Isolate;
3131using v8::Local;
32+ using v8::Maybe;
33+ using v8::MaybeLocal;
3234using v8::Object;
35+ using v8::String;
3336using v8::Value;
3437
3538using v8_inspector::StringBuffer;
@@ -449,7 +452,9 @@ Agent::Agent(Environment* env) : parent_env_(env),
449452 client_(nullptr ),
450453 platform_(nullptr ),
451454 enabled_(false ),
452- next_context_number_(1 ) {}
455+ next_context_number_(1 ),
456+ pending_enable_async_hook_(false ),
457+ pending_disable_async_hook_(false ) {}
453458
454459// Destructor needs to be defined here in implementation file as the header
455460// does not have full definition of some classes.
@@ -497,18 +502,6 @@ bool Agent::StartIoThread(bool wait_for_connect) {
497502 v8::Isolate* isolate = parent_env_->isolate ();
498503 HandleScope handle_scope (isolate);
499504
500- // Enable tracking of async stack traces
501- if (!enable_async_hook_function_.IsEmpty ()) {
502- Local<Function> enable_fn = enable_async_hook_function_.Get (isolate);
503- auto context = parent_env_->context ();
504- auto result = enable_fn->Call (context, Undefined (isolate), 0 , nullptr );
505- if (result.IsEmpty ()) {
506- FatalError (
507- " node::InspectorAgent::StartIoThread" ,
508- " Cannot enable Inspector's AsyncHook, please report this." );
509- }
510- }
511-
512505 // Send message to enable debug in workers
513506 Local<Object> process_object = parent_env_->process_object ();
514507 Local<Value> emit_fn =
@@ -554,20 +547,6 @@ void Agent::Stop() {
554547}
555548
556549void Agent::Connect (InspectorSessionDelegate* delegate) {
557- if (!enabled_) {
558- // Enable tracking of async stack traces
559- v8::Isolate* isolate = parent_env_->isolate ();
560- HandleScope handle_scope (isolate);
561- auto context = parent_env_->context ();
562- Local<Function> enable_fn = enable_async_hook_function_.Get (isolate);
563- auto result = enable_fn->Call (context, Undefined (isolate), 0 , nullptr );
564- if (result.IsEmpty ()) {
565- FatalError (
566- " node::InspectorAgent::Connect" ,
567- " Cannot enable Inspector's AsyncHook, please report this." );
568- }
569- }
570-
571550 enabled_ = true ;
572551 client_->connectFrontend (delegate);
573552}
@@ -593,6 +572,7 @@ void Agent::FatalException(Local<Value> error, Local<v8::Message> message) {
593572
594573void Agent::Dispatch (const StringView& message) {
595574 CHECK_NE (client_, nullptr );
575+ InterceptAsyncStackDepthMessage (message);
596576 client_->dispatchMessageFromFrontend (message);
597577}
598578
@@ -625,6 +605,37 @@ void Agent::RegisterAsyncHook(Isolate* isolate,
625605 v8::Local<v8::Function> disable_function) {
626606 enable_async_hook_function_.Reset (isolate, enable_function);
627607 disable_async_hook_function_.Reset (isolate, disable_function);
608+ if (pending_enable_async_hook_) {
609+ pending_enable_async_hook_ = false ;
610+ EnableAsyncHook (isolate);
611+ } else if (pending_disable_async_hook_) {
612+ pending_disable_async_hook_ = false ;
613+ DisableAsyncHook (isolate);
614+ }
615+ }
616+
617+ void Agent::EnableAsyncHook (Isolate* isolate) {
618+ CHECK (!enable_async_hook_function_.IsEmpty ());
619+ Local<Function> enable_fn = enable_async_hook_function_.Get (isolate);
620+ auto context = parent_env_->context ();
621+ auto result = enable_fn->Call (context, Undefined (isolate), 0 , nullptr );
622+ if (result.IsEmpty ()) {
623+ FatalError (
624+ " node::inspector::Agent::EnableAsyncHook" ,
625+ " Cannot enable Inspector's AsyncHook, please report this." );
626+ }
627+ }
628+
629+ void Agent::DisableAsyncHook (Isolate* isolate) {
630+ CHECK (!disable_async_hook_function_.IsEmpty ());
631+ Local<Function> disable_fn = disable_async_hook_function_.Get (isolate);
632+ auto context = parent_env_->context ();
633+ auto result = disable_fn->Call (context, Undefined (isolate), 0 , nullptr );
634+ if (result.IsEmpty ()) {
635+ FatalError (
636+ " node::inspector::Agent::DisableAsyncHook" ,
637+ " Cannot disable Inspector's AsyncHook, please report this." );
638+ }
628639}
629640
630641void Agent::AsyncTaskScheduled (const StringView& task_name, void * task,
@@ -648,6 +659,101 @@ void Agent::AllAsyncTasksCanceled() {
648659 client_->AllAsyncTasksCanceled ();
649660}
650661
662+ void Agent::InterceptAsyncStackDepthMessage (const StringView& message) {
663+ // This logic would be better implemented in JavaScript, but when using
664+ // --inspect-brk, the debugger must necessarily attach before much JavaScript
665+ // can execute. The Debugger.setAsyncCallStackDepth message arrives too early
666+ // and we must intercept this in C++.
667+
668+ v8::Isolate* isolate = parent_env_->isolate ();
669+ Local<Context> context = parent_env_->context ();
670+
671+ MaybeLocal<String> string =
672+ String::NewFromTwoByte (isolate, message.characters16 (),
673+ v8::NewStringType::kNormal , message.length ());
674+ if (string.IsEmpty ()) {
675+ return ;
676+ }
677+
678+ // Basically, the logic we want to implement is:
679+ // let parsed; try {
680+ // parsed = JSON.parse(string);
681+ // } catch (e) { return; }
682+ // if (parsed && parsed.method && parsed.method === 'Debugger.setAsync..' &&
683+ // parsed.params && parsed.params.maxDepth &&
684+ // typeof parsed.params.maxDepth === 'number') {
685+ // // Enable or Disable, depending on maxDepth.
686+ // }
687+ //
688+ // We ignore (return early) on malformed messages and let v8-inspector deal
689+ // with them.
690+
691+ MaybeLocal<Value> maybe_parsed =
692+ v8::JSON::Parse (context, string.ToLocalChecked ());
693+ if (maybe_parsed.IsEmpty ()) {
694+ return ;
695+ }
696+
697+ Local<Value> parsed = maybe_parsed.ToLocalChecked ();
698+ if (!parsed->IsObject ()) {
699+ return ;
700+ }
701+
702+ Local<Object> object = parsed.As <Object>();
703+
704+ Local<Value> method;
705+ if (!object->Get (context, parent_env_->method_string ()).ToLocal (&method) ||
706+ !method->IsString () ||
707+ !method->StrictEquals (parent_env_->async_stack_depth_string ())) {
708+ return ;
709+ }
710+
711+ Local<Value> params;
712+ if (!object->Get (context, parent_env_->params_string ()).ToLocal (¶ms) ||
713+ !params->IsObject ()) {
714+ return ;
715+ }
716+
717+ Local<Value> depth_value;
718+ if (!params.As <Object>()->Get (context, parent_env_->max_depth_string ())
719+ .ToLocal (&depth_value) ||
720+ !depth_value->IsNumber ()) {
721+ return ;
722+ }
723+
724+ Maybe<double > maybe_depth = depth_value->NumberValue (context);
725+ if (maybe_depth.IsNothing ()) {
726+ return ;
727+ }
728+
729+ double depth = maybe_depth.FromJust ();
730+ if (depth == 0 ) {
731+ // Disable.
732+ if (!disable_async_hook_function_.IsEmpty ()) {
733+ DisableAsyncHook (isolate);
734+ } else {
735+ if (pending_enable_async_hook_) {
736+ CHECK (!pending_disable_async_hook_);
737+ pending_enable_async_hook_ = false ;
738+ } else {
739+ pending_disable_async_hook_ = true ;
740+ }
741+ }
742+ } else {
743+ // Enable.
744+ if (!enable_async_hook_function_.IsEmpty ()) {
745+ EnableAsyncHook (isolate);
746+ } else {
747+ if (pending_disable_async_hook_) {
748+ CHECK (!pending_enable_async_hook_);
749+ pending_disable_async_hook_ = false ;
750+ } else {
751+ pending_enable_async_hook_ = true ;
752+ }
753+ }
754+ }
755+ }
756+
651757void Agent::RequestIoThreadStart () {
652758 // We need to attempt to interrupt V8 flow (in case Node is running
653759 // continuous JS code) and to wake up libuv thread (in case Node is waiting
0 commit comments