@@ -547,11 +547,7 @@ class NodeInspectorClient : public V8InspectorClient {
547547 return ;
548548 }
549549 if (auto agent = env_->inspector_agent ()) {
550- if (depth == 0 ) {
551- agent->DisableAsyncHook ();
552- } else {
553- agent->EnableAsyncHook ();
554- }
550+ agent->SetAsyncHookTrackingEnabled (depth != 0 );
555551 }
556552 }
557553
@@ -647,6 +643,7 @@ class NodeInspectorClient : public V8InspectorClient {
647643
648644 void installAdditionalCommandLineAPI (Local<Context> context,
649645 Local<Object> target) override {
646+ if (!env_->can_call_into_js ()) return ;
650647 Local<Function> installer = env_->inspector_console_extension_installer ();
651648 if (!installer.IsEmpty ()) {
652649 Local<Value> argv[] = {target};
@@ -1068,58 +1065,69 @@ void Agent::RegisterAsyncHook(Isolate* isolate,
10681065 Local<Function> disable_function) {
10691066 parent_env_->set_inspector_enable_async_hooks (enable_function);
10701067 parent_env_->set_inspector_disable_async_hooks (disable_function);
1071- if (pending_enable_async_hook_) {
1072- CHECK (!pending_disable_async_hook_);
1073- pending_enable_async_hook_ = false ;
1074- EnableAsyncHook ();
1075- } else if (pending_disable_async_hook_) {
1076- CHECK (!pending_enable_async_hook_);
1077- pending_disable_async_hook_ = false ;
1078- DisableAsyncHook ();
1079- }
1068+ SyncAsyncHookState ();
10801069}
10811070
1082- void Agent::EnableAsyncHook () {
1083- HandleScope scope (parent_env_->isolate ());
1084- Local<Function> enable = parent_env_->inspector_enable_async_hooks ();
1085- if (!enable.IsEmpty ()) {
1086- ToggleAsyncHook (parent_env_->isolate (), enable);
1087- } else if (pending_disable_async_hook_) {
1088- CHECK (!pending_enable_async_hook_);
1089- pending_disable_async_hook_ = false ;
1090- } else {
1091- pending_enable_async_hook_ = true ;
1092- }
1071+ void Agent::SetAsyncHookTrackingEnabled (bool enabled) {
1072+ async_hook_wanted_ = enabled;
1073+ SyncAsyncHookState ();
10931074}
10941075
1095- void Agent::DisableAsyncHook () {
1096- HandleScope scope (parent_env_->isolate ());
1097- Local<Function> disable = parent_env_->inspector_disable_async_hooks ();
1098- if (!disable.IsEmpty ()) {
1099- ToggleAsyncHook (parent_env_->isolate (), disable);
1100- } else if (pending_enable_async_hook_) {
1101- CHECK (!pending_disable_async_hook_);
1102- pending_enable_async_hook_ = false ;
1103- } else {
1104- pending_disable_async_hook_ = true ;
1105- }
1106- }
1076+ // Reconcile the state of the async hook used for async stack traces with the
1077+ // state last requested by the protocol. The hook is set up in JS land,
1078+ // (see inspector_async_hooks.js), which isn't safe to do when:
1079+ // 1. We are in early bootstrap and the setup functions aren't registered in
1080+ // C++ yet.
1081+ // 2. We are in a V8 interrupt requested by inspector protocol message
1082+ // dispatch e.g. from maxAsyncCallStackDepthChanged() notifications.
1083+ // When it's not safe to call into JS, this is a no-op and we'll try again in
1084+ // RegisterAsyncHook() (for 1) or from a scheduled immediate (for 2).
1085+ void Agent::SyncAsyncHookState () {
1086+ // The debugger can request an interrupt within the toggle JS function itself,
1087+ // A nested call only records the new requested state, the outermost call sees
1088+ // it when re-checking the loop condition after each toggle.
1089+ if (syncing_async_hook_state_) return ;
1090+ syncing_async_hook_state_ = true ;
1091+ auto on_exit = OnScopeLeave ([this ]() { syncing_async_hook_state_ = false ; });
1092+
1093+ Isolate* isolate = parent_env_->isolate ();
1094+ HandleScope scope (isolate);
1095+ while (async_hook_wanted_ != async_hook_enabled_) {
1096+ // Guard against running this during cleanup -- no async events will be
1097+ // emitted anyway at that point anymore, and calling into JS is not
1098+ // possible. This should probably not be something we're attempting in the
1099+ // first place,
1100+ // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
1101+ if (!parent_env_->can_call_into_js ()) return ;
1102+
1103+ bool enable = async_hook_wanted_;
1104+ Local<Function> fn = enable ? parent_env_->inspector_enable_async_hooks ()
1105+ : parent_env_->inspector_disable_async_hooks ();
1106+ if (fn.IsEmpty ()) return ;
1107+
1108+ if (parent_env_->is_processing_v8_interrupt ()) {
1109+ parent_env_->SetImmediate (
1110+ [](Environment* env) {
1111+ Agent* agent = env->inspector_agent ();
1112+ if (agent != nullptr ) agent->SyncAsyncHookState ();
1113+ },
1114+ CallbackFlags::kUnrefed );
1115+ return ;
1116+ }
11071117
1108- void Agent::ToggleAsyncHook (Isolate* isolate, Local<Function> fn) {
1109- // Guard against running this during cleanup -- no async events will be
1110- // emitted anyway at that point anymore, and calling into JS is not possible.
1111- // This should probably not be something we're attempting in the first place,
1112- // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
1113- if (!parent_env_->can_call_into_js ()) return ;
1114- CHECK (parent_env_->has_run_bootstrapping_code ());
1115- HandleScope handle_scope (isolate);
1116- CHECK (!fn.IsEmpty ());
1117- auto context = parent_env_->context ();
1118- v8::TryCatch try_catch (isolate);
1119- USE (fn->Call (context, Undefined (isolate), 0 , nullptr ));
1120- if (try_catch.HasCaught () && !try_catch.HasTerminated ()) {
1121- PrintCaughtException (isolate, context, try_catch);
1122- UNREACHABLE (" Cannot toggle Inspector's AsyncHook, please report this." );
1118+ CHECK (parent_env_->has_run_bootstrapping_code ());
1119+ Local<Context> context = parent_env_->context ();
1120+ v8::TryCatch try_catch (isolate);
1121+ USE (fn->Call (context, Undefined (isolate), 0 , nullptr ));
1122+ if (try_catch.HasCaught ()) {
1123+ // Termination may abort the toggle invocation, retrying now would just
1124+ // be terminated again. Instead of recording the toggle that may not have
1125+ // taken effect, leave the states as-is so that a later sync retries.
1126+ if (try_catch.HasTerminated ()) return ;
1127+ PrintCaughtException (isolate, context, try_catch);
1128+ UNREACHABLE (" Cannot toggle Inspector's AsyncHook, please report this." );
1129+ }
1130+ async_hook_enabled_ = enable;
11231131 }
11241132}
11251133
0 commit comments