@@ -547,11 +547,7 @@ class NodeInspectorClient : public V8InspectorClient { |
547 | 547 | return; |
548 | 548 | } |
549 | 549 | if (auto agent = env_->inspector_agent()) { |
550 | | - if (depth == 0) { |
551 | | - agent->DisableAsyncHook(); |
552 | | - } else { |
553 | | - agent->EnableAsyncHook(); |
554 | | - } |
| 550 | + agent->SetAsyncHookTrackingEnabled(depth != 0); |
555 | 551 | } |
556 | 552 | } |
557 | 553 |
|
@@ -647,6 +643,7 @@ class NodeInspectorClient : public V8InspectorClient { |
647 | 643 |
|
648 | 644 | void installAdditionalCommandLineAPI(Local<Context> context, |
649 | 645 | Local<Object> target) override { |
| 646 | + if (!env_->can_call_into_js()) return; |
650 | 647 | Local<Function> installer = env_->inspector_console_extension_installer(); |
651 | 648 | if (!installer.IsEmpty()) { |
652 | 649 | Local<Value> argv[] = {target}; |
@@ -1068,58 +1065,69 @@ void Agent::RegisterAsyncHook(Isolate* isolate, |
1068 | 1065 | Local<Function> disable_function) { |
1069 | 1066 | parent_env_->set_inspector_enable_async_hooks(enable_function); |
1070 | 1067 | 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(); |
1080 | 1069 | } |
1081 | 1070 |
|
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(); |
1093 | 1074 | } |
1094 | 1075 |
|
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 | + } |
1107 | 1117 |
|
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; |
1123 | 1131 | } |
1124 | 1132 | } |
1125 | 1133 |
|
|