@@ -16,16 +16,16 @@ using namespace v8;
1616using namespace node ;
1717using namespace std ::chrono;
1818
19- static const int kMaxStackFrames = 255 ;
19+ static const int kMaxStackFrames = 50 ;
2020
2121// Structure to hold information for each thread/isolate
2222struct ThreadInfo {
2323 // Thread name
2424 std::string thread_name;
2525 // Last time this thread was seen in milliseconds since epoch
2626 milliseconds last_seen;
27- // Some JSON serialized state for the thread
28- std::string state ;
27+ // Async local storage associated with this thread
28+ v8::Global<v8::Value> async_local_storage ;
2929};
3030
3131static std::mutex threads_mutex;
@@ -41,21 +41,26 @@ struct JsStackFrame {
4141};
4242
4343// Type alias for a vector of JsStackFrame
44- using JsStackTrace = std::vector<JsStackFrame>;
44+ using JsStackFrames = std::vector<JsStackFrame>;
45+
46+ struct JsStackTrace {
47+ // The frames in the stack trace
48+ std::vector<JsStackFrame> frames;
49+ // JSON serialized string of the state
50+ std::string state;
51+ };
4552
4653struct ThreadResult {
4754 std::string thread_name;
48- std::string state;
49- JsStackTrace stack_frames;
55+ JsStackTrace stack_trace;
5056};
5157
52- // Function to be called when an isolate's execution is interrupted
53- static void ExecutionInterrupted (Isolate *isolate, void *data) {
54- auto promise = static_cast <std::promise<JsStackTrace> *>(data);
58+ // Function to get stack frames from a V8 stack trace
59+ JsStackFrames GetStackFrames (Isolate *isolate) {
5560 auto stack = StackTrace::CurrentStackTrace (isolate, kMaxStackFrames ,
5661 StackTrace::kDetailed );
5762
58- JsStackTrace frames;
63+ JsStackFrames frames;
5964 if (!stack.IsEmpty ()) {
6065 for (int i = 0 ; i < stack->GetFrameCount (); i++) {
6166 auto frame = stack->GetFrame (isolate, i);
@@ -89,18 +94,76 @@ static void ExecutionInterrupted(Isolate *isolate, void *data) {
8994 }
9095 }
9196
92- promise->set_value (frames);
97+ return frames;
98+ }
99+
100+ static std::string ToJSONOrEmpty (Isolate *isolate, v8::Local<v8::Value> value) {
101+ auto context = isolate->GetCurrentContext ();
102+ MaybeLocal<String> maybe_json = v8::JSON::Stringify (context, value);
103+
104+ if (!maybe_json.IsEmpty ()) {
105+ v8::String::Utf8Value utf8_state (isolate, maybe_json.ToLocalChecked ());
106+ if (*utf8_state) {
107+ return *utf8_state;
108+ }
109+ }
110+ return " " ;
111+ }
112+
113+ // Function to fetch the thread state from the async context store
114+ std::string GetThreadState (Isolate *isolate,
115+ v8::Global<v8::Value> async_local_storage) {
116+ // Node.js stores the async local storage in the isolate's
117+ // "ContinuationPreservedEmbedderData" map, keyed by the
118+ // AsyncLocalStorage instance.
119+ // https://github.com/nodejs/node/blob/c6316f9db9869864cea84e5f07585fa08e3e06d2/src/async_context_frame.cc#L37
120+ auto data = isolate->GetContinuationPreservedEmbedderData ();
121+ auto local_async_local_storage = async_local_storage.Get (isolate);
122+
123+ if (!data.IsEmpty () && data->IsMap ()) {
124+ auto map = data.As <v8::Map>();
125+ auto context = isolate->GetCurrentContext ();
126+ auto val = map->Get (context, local_async_local_storage);
127+
128+ if (!val.IsEmpty ()) {
129+ return ToJSONOrEmpty (isolate, val.ToLocalChecked ());
130+ }
131+ }
132+
133+ return " " ;
134+ }
135+
136+ struct InterruptArgs {
137+ std::promise<JsStackTrace> *promise;
138+ v8::Global<v8::Value> async_local_storage;
139+ };
140+
141+ // Function to be called when an isolate's execution is interrupted
142+ static void ExecutionInterrupted (Isolate *isolate, void *data) {
143+ auto args = static_cast <InterruptArgs *>(data);
144+
145+ v8::Locker locker (isolate);
146+ v8::HandleScope scope (isolate);
147+
148+ auto frames = GetStackFrames (isolate);
149+ auto state = GetThreadState (isolate, std::move (args->async_local_storage ));
150+
151+ args->promise ->set_value ({frames, state});
93152}
94153
95154// Function to capture the stack trace of a single isolate
96- JsStackTrace CaptureStackTrace (Isolate *isolate) {
155+ JsStackTrace CaptureStackTrace (Isolate *isolate,
156+ v8::Global<v8::Value> async_local_storage) {
97157 std::promise<JsStackTrace> promise;
98- auto future = promise.get_future ();
99158
100159 // The v8 isolate must be interrupted to capture the stack trace
101160 // Execution resumes automatically after ExecutionInterrupted returns
102- isolate->RequestInterrupt (ExecutionInterrupted, &promise);
103- return future.get ();
161+ isolate->RequestInterrupt (
162+ ExecutionInterrupted,
163+ new InterruptArgs{&promise,
164+ v8::Global<v8::Value>(isolate, async_local_storage)});
165+
166+ return promise.get_future ().get ();
104167}
105168
106169// Function to capture stack traces from all registered threads
@@ -112,21 +175,27 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
112175
113176 {
114177 std::lock_guard<std::mutex> lock (threads_mutex);
115- for (auto [thread_isolate, thread_info] : threads) {
178+ for (auto & [thread_isolate, thread_info] : threads) {
116179 if (thread_isolate == capture_from_isolate)
117180 continue ;
181+
118182 auto thread_name = thread_info.thread_name ;
119- auto state = thread_info.state ;
120183
121184 futures.emplace_back (std::async (
122185 std::launch::async,
123- [thread_name, state](Isolate *isolate) -> ThreadResult {
124- return ThreadResult{thread_name, state, CaptureStackTrace (isolate)};
186+ [thread_isolate, thread_name](
187+ v8::Global<v8::Value> async_local_storage) -> ThreadResult {
188+ return ThreadResult{
189+ thread_name, CaptureStackTrace (thread_isolate,
190+ std::move (async_local_storage))};
125191 },
126- thread_isolate ));
192+ std::move (thread_info. async_local_storage ) ));
127193 }
128194 }
129195
196+ v8::Locker locker (capture_from_isolate);
197+ v8::HandleScope scope (capture_from_isolate);
198+
130199 Local<Object> output = Object::New (capture_from_isolate);
131200
132201 for (auto &future : futures) {
@@ -137,9 +206,9 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
137206 .ToLocalChecked ();
138207
139208 Local<Array> jsFrames =
140- Array::New (capture_from_isolate, result.stack_frames .size ());
141- for (size_t i = 0 ; i < result.stack_frames .size (); ++i) {
142- const auto &frame = result.stack_frames [i];
209+ Array::New (capture_from_isolate, result.stack_trace . frames .size ());
210+ for (size_t i = 0 ; i < result.stack_trace . frames .size (); ++i) {
211+ const auto &frame = result.stack_trace . frames [i];
143212 Local<Object> frameObj = Object::New (capture_from_isolate);
144213 frameObj
145214 ->Set (current_context,
@@ -189,9 +258,10 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
189258 jsFrames)
190259 .Check ();
191260
192- if (!result.state .empty ()) {
261+ if (!result.stack_trace . state .empty ()) {
193262 v8::MaybeLocal<v8::String> stateStr = v8::String::NewFromUtf8 (
194- capture_from_isolate, result.state .c_str (), NewStringType::kNormal );
263+ capture_from_isolate, result.stack_trace .state .c_str (),
264+ NewStringType::kNormal );
195265 if (!stateStr.IsEmpty ()) {
196266 v8::MaybeLocal<v8::Value> maybeStateVal =
197267 v8::JSON::Parse (current_context, stateStr.ToLocalChecked ());
@@ -226,7 +296,7 @@ void Cleanup(void *arg) {
226296void RegisterThread (const FunctionCallbackInfo<Value> &args) {
227297 auto isolate = args.GetIsolate ();
228298
229- if (args.Length () != 1 || !args[0 ]->IsString ()) {
299+ if (args.Length () < 1 || !args[0 ]->IsString ()) {
230300 isolate->ThrowException (Exception::Error (
231301 String::NewFromUtf8 (
232302 isolate, " registerThread(name) requires a single name argument" ,
@@ -236,15 +306,20 @@ void RegisterThread(const FunctionCallbackInfo<Value> &args) {
236306 return ;
237307 }
238308
309+ v8::Global<v8::Value> async_local_storage;
310+ if (args.Length () > 1 ) {
311+ async_local_storage.Reset (isolate, args[1 ]);
312+ }
313+
239314 v8::String::Utf8Value utf8 (isolate, args[0 ]);
240315 std::string thread_name (*utf8 ? *utf8 : " " );
241316
242317 {
243318 std::lock_guard<std::mutex> lock (threads_mutex);
244319 auto found = threads.find (isolate);
245320 if (found == threads.end ()) {
246- threads.emplace (isolate,
247- ThreadInfo{thread_name, milliseconds::zero (), " " });
321+ threads.emplace (isolate, ThreadInfo{thread_name, milliseconds::zero (),
322+ std::move (async_local_storage) });
248323 // Register a cleanup hook to remove this thread when the isolate is
249324 // destroyed
250325 node::AddEnvironmentCleanupHook (isolate, Cleanup, isolate);
@@ -280,37 +355,22 @@ steady_clock::time_point GetUnbiasedMonotonicTime() {
280355// Function to track a thread and set its state
281356void ThreadPoll (const FunctionCallbackInfo<Value> &args) {
282357 auto isolate = args.GetIsolate ();
283- auto context = isolate->GetCurrentContext ();
284-
285- std::string state_str;
286- if (args.Length () > 0 && args[0 ]->IsValue ()) {
287- MaybeLocal<String> maybe_json = v8::JSON::Stringify (context, args[0 ]);
288- if (!maybe_json.IsEmpty ()) {
289- v8::String::Utf8Value utf8_state (isolate, maybe_json.ToLocalChecked ());
290- state_str = *utf8_state ? *utf8_state : " " ;
291- } else {
292- state_str = " " ;
293- }
294- } else {
295- state_str = " " ;
296- }
297358
298- bool disable_last_seen = false ;
299- if (args.Length () > 1 && args[1 ]->IsBoolean ()) {
300- disable_last_seen = args[1 ]->BooleanValue (isolate);
359+ bool enable_last_seen = true ;
360+ if (args.Length () > 0 && args[0 ]->IsBoolean ()) {
361+ enable_last_seen = args[0 ]->BooleanValue (isolate);
301362 }
302363
303364 {
304365 std::lock_guard<std::mutex> lock (threads_mutex);
305366 auto found = threads.find (isolate);
306367 if (found != threads.end ()) {
307368 auto &thread_info = found->second ;
308- thread_info.state = state_str;
309- if (disable_last_seen) {
310- thread_info.last_seen = milliseconds::zero ();
311- } else {
369+ if (enable_last_seen) {
312370 thread_info.last_seen = duration_cast<milliseconds>(
313371 GetUnbiasedMonotonicTime ().time_since_epoch ());
372+ } else {
373+ thread_info.last_seen = milliseconds::zero ();
314374 }
315375 }
316376 }
0 commit comments