@@ -79,6 +79,52 @@ static std::atomic_bool start_io_thread_async_initialized { false };
7979// Protects the Agent* stored in start_io_thread_async.data.
8080static Mutex start_io_thread_async_mutex;
8181
82+ template <typename Defer, typename OnFailure>
83+ void SyncJavaScriptHookState (Environment* env,
84+ JavaScriptHookState* state,
85+ Local<Function> enable_function,
86+ Local<Function> disable_function,
87+ bool require_bootstrapping,
88+ Defer defer,
89+ OnFailure on_failure) {
90+ // The debugger can request an interrupt from within a hook's JS toggle
91+ // function. A nested sync only records the new requested state; the
92+ // outermost sync sees it when re-checking the loop after each toggle.
93+ if (state->syncing ) return ;
94+ state->syncing = true ;
95+ auto on_exit = OnScopeLeave ([state]() { state->syncing = false ; });
96+
97+ Isolate* isolate = env->isolate ();
98+ while (state->wanted != state->enabled ) {
99+ // No hook events will be emitted during cleanup, and calling into JS is no
100+ // longer possible.
101+ // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
102+ if (!env->can_call_into_js ()) return ;
103+
104+ bool enable = state->wanted ;
105+ Local<Function> function = enable ? enable_function : disable_function;
106+ if (function.IsEmpty ()) return ;
107+
108+ if (env->is_processing_v8_interrupt ()) {
109+ defer ();
110+ return ;
111+ }
112+
113+ if (require_bootstrapping) CHECK (env->has_run_bootstrapping_code ());
114+ Local<Context> context = env->context ();
115+ v8::TryCatch try_catch (isolate);
116+ USE (function->Call (context, Undefined (isolate), 0 , nullptr ));
117+ if (try_catch.HasCaught ()) {
118+ // Termination may abort the toggle invocation. Do not record a state that
119+ // may not have taken effect, so a later sync can retry it.
120+ if (try_catch.HasTerminated ()) return ;
121+ PrintCaughtException (isolate, context, try_catch);
122+ on_failure ();
123+ }
124+ state->enabled = enable;
125+ }
126+ }
127+
82128// Called on the main thread.
83129void StartIoThreadAsyncCallback (uv_async_t * handle) {
84130 static_cast <Agent*>(handle->data )->StartIoThread ();
@@ -475,6 +521,18 @@ class SameThreadInspectorSession : public InspectorSession {
475521
476522void NotifyClusterWorkersDebugEnabled (Environment* env) {
477523 Isolate* isolate = env->isolate ();
524+
525+ // Starting the inspector from an interrupt is safe, but emitting the cluster
526+ // notification calls into JS.
527+ if (env->is_processing_v8_interrupt ()) {
528+ env->SetImmediate (
529+ [](Environment* env) {
530+ NotifyClusterWorkersDebugEnabled (env);
531+ },
532+ CallbackFlags::kUnrefed );
533+ return ;
534+ }
535+
478536 HandleScope handle_scope (isolate);
479537
480538 // Send message to enable debug in cluster workers
@@ -979,54 +1037,40 @@ void Agent::SetupNetworkTracking(Local<Function> enable_function,
9791037 Local<Function> disable_function) {
9801038 parent_env_->set_inspector_enable_network_tracking (enable_function);
9811039 parent_env_->set_inspector_disable_network_tracking (disable_function);
982- if (pending_enable_network_tracking) {
983- pending_enable_network_tracking = false ;
984- EnableNetworkTracking ();
985- } else if (pending_disable_network_tracking) {
986- pending_disable_network_tracking = false ;
987- DisableNetworkTracking ();
988- }
1040+ SyncNetworkTrackingState ();
9891041}
9901042
9911043void Agent::EnableNetworkTracking () {
992- if (network_tracking_enabled_) {
993- return ;
994- }
995- HandleScope scope (parent_env_->isolate ());
996- Local<Function> enable = parent_env_->inspector_enable_network_tracking ();
997- if (enable.IsEmpty ()) {
998- pending_enable_network_tracking = true ;
999- } else {
1000- ToggleNetworkTracking (parent_env_->isolate (), enable);
1001- network_tracking_enabled_ = true ;
1002- }
1044+ network_tracking_state_.wanted = true ;
1045+ SyncNetworkTrackingState ();
10031046}
10041047
10051048void Agent::DisableNetworkTracking () {
1006- if (!network_tracking_enabled_) {
1007- return ;
1008- }
1009- HandleScope scope (parent_env_->isolate ());
1010- Local<Function> disable = parent_env_->inspector_disable_network_tracking ();
1011- if (disable.IsEmpty ()) {
1012- pending_disable_network_tracking = true ;
1013- } else if (!client_->hasConnectedSessions ()) {
1014- ToggleNetworkTracking (parent_env_->isolate (), disable);
1015- network_tracking_enabled_ = false ;
1016- }
1049+ if (client_->hasConnectedSessions ()) return ;
1050+ network_tracking_state_.wanted = false ;
1051+ SyncNetworkTrackingState ();
10171052}
10181053
1019- void Agent::ToggleNetworkTracking (Isolate* isolate, Local<Function> fn) {
1020- if (!parent_env_->can_call_into_js ()) return ;
1021- auto context = parent_env_->context ();
1054+ void Agent::SyncNetworkTrackingState () {
1055+ Isolate* isolate = parent_env_->isolate ();
10221056 HandleScope scope (isolate);
1023- CHECK (!fn.IsEmpty ());
1024- v8::TryCatch try_catch (isolate);
1025- USE (fn->Call (context, Undefined (isolate), 0 , nullptr ));
1026- if (try_catch.HasCaught () && !try_catch.HasTerminated ()) {
1027- PrintCaughtException (isolate, context, try_catch);
1028- UNREACHABLE (" Cannot toggle network tracking, please report this." );
1029- }
1057+ SyncJavaScriptHookState (
1058+ parent_env_,
1059+ &network_tracking_state_,
1060+ parent_env_->inspector_enable_network_tracking (),
1061+ parent_env_->inspector_disable_network_tracking (),
1062+ false ,
1063+ [this ]() {
1064+ parent_env_->SetImmediate (
1065+ [](Environment* env) {
1066+ Agent* agent = env->inspector_agent ();
1067+ if (agent != nullptr ) agent->SyncNetworkTrackingState ();
1068+ },
1069+ CallbackFlags::kUnrefed );
1070+ },
1071+ []() {
1072+ UNREACHABLE (" Cannot toggle network tracking, please report this." );
1073+ });
10301074}
10311075
10321076void Agent::WaitForDisconnect () {
@@ -1077,7 +1121,7 @@ void Agent::RegisterAsyncHook(Isolate* isolate,
10771121}
10781122
10791123void Agent::SetAsyncHookTrackingEnabled (bool enabled) {
1080- async_hook_wanted_ = enabled;
1124+ async_hook_state_. wanted = enabled;
10811125 SyncAsyncHookState ();
10821126}
10831127
@@ -1091,52 +1135,25 @@ void Agent::SetAsyncHookTrackingEnabled(bool enabled) {
10911135// When it's not safe to call into JS, this is a no-op and we'll try again in
10921136// RegisterAsyncHook() (for 1) or from a scheduled immediate (for 2).
10931137void Agent::SyncAsyncHookState () {
1094- // The debugger can request an interrupt within the toggle JS function itself,
1095- // A nested call only records the new requested state, the outermost call sees
1096- // it when re-checking the loop condition after each toggle.
1097- if (syncing_async_hook_state_) return ;
1098- syncing_async_hook_state_ = true ;
1099- auto on_exit = OnScopeLeave ([this ]() { syncing_async_hook_state_ = false ; });
1100-
11011138 Isolate* isolate = parent_env_->isolate ();
11021139 HandleScope scope (isolate);
1103- while (async_hook_wanted_ != async_hook_enabled_) {
1104- // Guard against running this during cleanup -- no async events will be
1105- // emitted anyway at that point anymore, and calling into JS is not
1106- // possible. This should probably not be something we're attempting in the
1107- // first place,
1108- // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
1109- if (!parent_env_->can_call_into_js ()) return ;
1110-
1111- bool enable = async_hook_wanted_;
1112- Local<Function> fn = enable ? parent_env_->inspector_enable_async_hooks ()
1113- : parent_env_->inspector_disable_async_hooks ();
1114- if (fn.IsEmpty ()) return ;
1115-
1116- if (parent_env_->is_processing_v8_interrupt ()) {
1117- parent_env_->SetImmediate (
1118- [](Environment* env) {
1119- Agent* agent = env->inspector_agent ();
1120- if (agent != nullptr ) agent->SyncAsyncHookState ();
1121- },
1122- CallbackFlags::kUnrefed );
1123- return ;
1124- }
1125-
1126- CHECK (parent_env_->has_run_bootstrapping_code ());
1127- Local<Context> context = parent_env_->context ();
1128- v8::TryCatch try_catch (isolate);
1129- USE (fn->Call (context, Undefined (isolate), 0 , nullptr ));
1130- if (try_catch.HasCaught ()) {
1131- // Termination may abort the toggle invocation, retrying now would just
1132- // be terminated again. Instead of recording the toggle that may not have
1133- // taken effect, leave the states as-is so that a later sync retries.
1134- if (try_catch.HasTerminated ()) return ;
1135- PrintCaughtException (isolate, context, try_catch);
1136- UNREACHABLE (" Cannot toggle Inspector's AsyncHook, please report this." );
1137- }
1138- async_hook_enabled_ = enable;
1139- }
1140+ SyncJavaScriptHookState (
1141+ parent_env_,
1142+ &async_hook_state_,
1143+ parent_env_->inspector_enable_async_hooks (),
1144+ parent_env_->inspector_disable_async_hooks (),
1145+ true ,
1146+ [this ]() {
1147+ parent_env_->SetImmediate (
1148+ [](Environment* env) {
1149+ Agent* agent = env->inspector_agent ();
1150+ if (agent != nullptr ) agent->SyncAsyncHookState ();
1151+ },
1152+ CallbackFlags::kUnrefed );
1153+ },
1154+ []() {
1155+ UNREACHABLE (" Cannot toggle Inspector's AsyncHook, please report this." );
1156+ });
11401157}
11411158
11421159void Agent::AsyncTaskScheduled (const StringView& task_name, void * task,
0 commit comments