@@ -80,6 +80,52 @@ static Mutex start_io_thread_agents_mutex;
8080static std::vector<Agent*> start_io_thread_agents;
8181static bool debug_signal_handler_started = false ;
8282
83+ template <typename Defer, typename OnFailure>
84+ void SyncJavaScriptHookState (Environment* env,
85+ JavaScriptHookState* state,
86+ Local<Function> enable_function,
87+ Local<Function> disable_function,
88+ bool require_bootstrapping,
89+ Defer defer,
90+ OnFailure on_failure) {
91+ // The debugger can request an interrupt from within a hook's JS toggle
92+ // function. A nested sync only records the new requested state; the
93+ // outermost sync sees it when re-checking the loop after each toggle.
94+ if (state->syncing ) return ;
95+ state->syncing = true ;
96+ auto on_exit = OnScopeLeave ([state]() { state->syncing = false ; });
97+
98+ Isolate* isolate = env->isolate ();
99+ while (state->wanted != state->enabled ) {
100+ // No hook events will be emitted during cleanup, and calling into JS is no
101+ // longer possible.
102+ // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
103+ if (!env->can_call_into_js ()) return ;
104+
105+ bool enable = state->wanted ;
106+ Local<Function> function = enable ? enable_function : disable_function;
107+ if (function.IsEmpty ()) return ;
108+
109+ if (env->is_processing_v8_interrupt ()) {
110+ defer ();
111+ return ;
112+ }
113+
114+ if (require_bootstrapping) CHECK (env->has_run_bootstrapping_code ());
115+ Local<Context> context = env->context ();
116+ v8::TryCatch try_catch (isolate);
117+ USE (function->Call (context, Undefined (isolate), 0 , nullptr ));
118+ if (try_catch.HasCaught ()) {
119+ // Termination may abort the toggle invocation. Do not record a state that
120+ // may not have taken effect, so a later sync can retry it.
121+ if (try_catch.HasTerminated ()) return ;
122+ PrintCaughtException (isolate, context, try_catch);
123+ on_failure ();
124+ }
125+ state->enabled = enable;
126+ }
127+ }
128+
83129static void RequestIoThreadStartOnAgents () {
84130 Mutex::ScopedLock lock (start_io_thread_agents_mutex);
85131 for (Agent* agent : start_io_thread_agents) agent->RequestIoThreadStart ();
@@ -483,6 +529,16 @@ class SameThreadInspectorSession : public InspectorSession {
483529
484530void NotifyClusterWorkersDebugEnabled (Environment* env) {
485531 Isolate* isolate = env->isolate ();
532+
533+ // Starting the inspector from an interrupt is safe, but emitting the cluster
534+ // notification calls into JS.
535+ if (env->is_processing_v8_interrupt ()) {
536+ env->SetImmediate (
537+ [](Environment* env) { NotifyClusterWorkersDebugEnabled (env); },
538+ CallbackFlags::kUnrefed );
539+ return ;
540+ }
541+
486542 HandleScope handle_scope (isolate);
487543
488544 // Send message to enable debug in cluster workers
@@ -1000,54 +1056,40 @@ void Agent::SetupNetworkTracking(Local<Function> enable_function,
10001056 Local<Function> disable_function) {
10011057 parent_env_->set_inspector_enable_network_tracking (enable_function);
10021058 parent_env_->set_inspector_disable_network_tracking (disable_function);
1003- if (pending_enable_network_tracking) {
1004- pending_enable_network_tracking = false ;
1005- EnableNetworkTracking ();
1006- } else if (pending_disable_network_tracking) {
1007- pending_disable_network_tracking = false ;
1008- DisableNetworkTracking ();
1009- }
1059+ SyncNetworkTrackingState ();
10101060}
10111061
10121062void Agent::EnableNetworkTracking () {
1013- if (network_tracking_enabled_) {
1014- return ;
1015- }
1016- HandleScope scope (parent_env_->isolate ());
1017- Local<Function> enable = parent_env_->inspector_enable_network_tracking ();
1018- if (enable.IsEmpty ()) {
1019- pending_enable_network_tracking = true ;
1020- } else {
1021- ToggleNetworkTracking (parent_env_->isolate (), enable);
1022- network_tracking_enabled_ = true ;
1023- }
1063+ network_tracking_state_.wanted = true ;
1064+ SyncNetworkTrackingState ();
10241065}
10251066
10261067void Agent::DisableNetworkTracking () {
1027- if (!network_tracking_enabled_) {
1028- return ;
1029- }
1030- HandleScope scope (parent_env_->isolate ());
1031- Local<Function> disable = parent_env_->inspector_disable_network_tracking ();
1032- if (disable.IsEmpty ()) {
1033- pending_disable_network_tracking = true ;
1034- } else if (!client_->hasConnectedSessions ()) {
1035- ToggleNetworkTracking (parent_env_->isolate (), disable);
1036- network_tracking_enabled_ = false ;
1037- }
1068+ if (client_->hasConnectedSessions ()) return ;
1069+ network_tracking_state_.wanted = false ;
1070+ SyncNetworkTrackingState ();
10381071}
10391072
1040- void Agent::ToggleNetworkTracking (Isolate* isolate, Local<Function> fn) {
1041- if (!parent_env_->can_call_into_js ()) return ;
1042- auto context = parent_env_->context ();
1073+ void Agent::SyncNetworkTrackingState () {
1074+ Isolate* isolate = parent_env_->isolate ();
10431075 HandleScope scope (isolate);
1044- CHECK (!fn.IsEmpty ());
1045- v8::TryCatch try_catch (isolate);
1046- USE (fn->Call (context, Undefined (isolate), 0 , nullptr ));
1047- if (try_catch.HasCaught () && !try_catch.HasTerminated ()) {
1048- PrintCaughtException (isolate, context, try_catch);
1049- UNREACHABLE (" Cannot toggle network tracking, please report this." );
1050- }
1076+ SyncJavaScriptHookState (
1077+ parent_env_,
1078+ &network_tracking_state_,
1079+ parent_env_->inspector_enable_network_tracking (),
1080+ parent_env_->inspector_disable_network_tracking (),
1081+ false ,
1082+ [this ]() {
1083+ parent_env_->SetImmediate (
1084+ [](Environment* env) {
1085+ Agent* agent = env->inspector_agent ();
1086+ if (agent != nullptr ) agent->SyncNetworkTrackingState ();
1087+ },
1088+ CallbackFlags::kUnrefed );
1089+ },
1090+ []() {
1091+ UNREACHABLE (" Cannot toggle network tracking, please report this." );
1092+ });
10511093}
10521094
10531095void Agent::WaitForDisconnect () {
@@ -1098,7 +1140,7 @@ void Agent::RegisterAsyncHook(Isolate* isolate,
10981140}
10991141
11001142void Agent::SetAsyncHookTrackingEnabled (bool enabled) {
1101- async_hook_wanted_ = enabled;
1143+ async_hook_state_. wanted = enabled;
11021144 SyncAsyncHookState ();
11031145}
11041146
@@ -1112,52 +1154,25 @@ void Agent::SetAsyncHookTrackingEnabled(bool enabled) {
11121154// When it's not safe to call into JS, this is a no-op and we'll try again in
11131155// RegisterAsyncHook() (for 1) or from a scheduled immediate (for 2).
11141156void Agent::SyncAsyncHookState () {
1115- // The debugger can request an interrupt within the toggle JS function itself,
1116- // A nested call only records the new requested state, the outermost call sees
1117- // it when re-checking the loop condition after each toggle.
1118- if (syncing_async_hook_state_) return ;
1119- syncing_async_hook_state_ = true ;
1120- auto on_exit = OnScopeLeave ([this ]() { syncing_async_hook_state_ = false ; });
1121-
11221157 Isolate* isolate = parent_env_->isolate ();
11231158 HandleScope scope (isolate);
1124- while (async_hook_wanted_ != async_hook_enabled_) {
1125- // Guard against running this during cleanup -- no async events will be
1126- // emitted anyway at that point anymore, and calling into JS is not
1127- // possible. This should probably not be something we're attempting in the
1128- // first place,
1129- // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
1130- if (!parent_env_->can_call_into_js ()) return ;
1131-
1132- bool enable = async_hook_wanted_;
1133- Local<Function> fn = enable ? parent_env_->inspector_enable_async_hooks ()
1134- : parent_env_->inspector_disable_async_hooks ();
1135- if (fn.IsEmpty ()) return ;
1136-
1137- if (parent_env_->is_processing_v8_interrupt ()) {
1138- parent_env_->SetImmediate (
1139- [](Environment* env) {
1140- Agent* agent = env->inspector_agent ();
1141- if (agent != nullptr ) agent->SyncAsyncHookState ();
1142- },
1143- CallbackFlags::kUnrefed );
1144- return ;
1145- }
1146-
1147- CHECK (parent_env_->has_run_bootstrapping_code ());
1148- Local<Context> context = parent_env_->context ();
1149- v8::TryCatch try_catch (isolate);
1150- USE (fn->Call (context, Undefined (isolate), 0 , nullptr ));
1151- if (try_catch.HasCaught ()) {
1152- // Termination may abort the toggle invocation, retrying now would just
1153- // be terminated again. Instead of recording the toggle that may not have
1154- // taken effect, leave the states as-is so that a later sync retries.
1155- if (try_catch.HasTerminated ()) return ;
1156- PrintCaughtException (isolate, context, try_catch);
1157- UNREACHABLE (" Cannot toggle Inspector's AsyncHook, please report this." );
1158- }
1159- async_hook_enabled_ = enable;
1160- }
1159+ SyncJavaScriptHookState (
1160+ parent_env_,
1161+ &async_hook_state_,
1162+ parent_env_->inspector_enable_async_hooks (),
1163+ parent_env_->inspector_disable_async_hooks (),
1164+ true ,
1165+ [this ]() {
1166+ parent_env_->SetImmediate (
1167+ [](Environment* env) {
1168+ Agent* agent = env->inspector_agent ();
1169+ if (agent != nullptr ) agent->SyncAsyncHookState ();
1170+ },
1171+ CallbackFlags::kUnrefed );
1172+ },
1173+ []() {
1174+ UNREACHABLE (" Cannot toggle Inspector's AsyncHook, please report this." );
1175+ });
11611176}
11621177
11631178void Agent::AsyncTaskScheduled (const StringView& task_name, void * task,
0 commit comments