@@ -15,6 +15,8 @@ struct ThreadInfo {
15
15
std::string thread_name;
16
16
// Last time this thread was seen in milliseconds since epoch
17
17
milliseconds last_seen;
18
+ // Some JSON serialized state for the thread
19
+ std::string state;
18
20
};
19
21
20
22
static std::mutex threads_mutex;
@@ -32,6 +34,12 @@ struct JsStackFrame {
32
34
// Type alias for a vector of JsStackFrame
33
35
using JsStackTrace = std::vector<JsStackFrame>;
34
36
37
+ struct ThreadResult {
38
+ std::string thread_name;
39
+ std::string state;
40
+ JsStackTrace stack_frames;
41
+ };
42
+
35
43
// Function to be called when an isolate's execution is interrupted
36
44
static void ExecutionInterrupted (Isolate *isolate, void *data) {
37
45
auto promise = static_cast <std::promise<JsStackTrace> *>(data);
@@ -91,7 +99,6 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
91
99
auto capture_from_isolate = args.GetIsolate ();
92
100
auto current_context = capture_from_isolate->GetCurrentContext ();
93
101
94
- using ThreadResult = std::tuple<std::string, JsStackTrace>;
95
102
std::vector<std::future<ThreadResult>> futures;
96
103
97
104
{
@@ -100,35 +107,38 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
100
107
if (thread_isolate == capture_from_isolate)
101
108
continue ;
102
109
auto thread_name = thread_info.thread_name ;
110
+ auto state = thread_info.state ;
103
111
104
112
futures.emplace_back (std::async (
105
113
std::launch::async,
106
- [thread_name](Isolate *isolate) -> ThreadResult {
107
- return std::make_tuple ( thread_name, CaptureStackTrace (isolate)) ;
114
+ [thread_name, state ](Isolate *isolate) -> ThreadResult {
115
+ return ThreadResult{ thread_name, state, CaptureStackTrace (isolate)} ;
108
116
},
109
117
thread_isolate));
110
118
}
111
119
}
112
120
113
- Local<Object> result = Object::New (capture_from_isolate);
121
+ Local<Object> output = Object::New (capture_from_isolate);
114
122
115
123
for (auto &future : futures) {
116
- auto [thread_name, frames] = future.get ();
117
- auto key = String::NewFromUtf8 (capture_from_isolate, thread_name.c_str (),
118
- NewStringType::kNormal )
119
- .ToLocalChecked ();
120
-
121
- Local<Array> jsFrames = Array::New (capture_from_isolate, frames.size ());
122
- for (size_t i = 0 ; i < frames.size (); ++i) {
123
- const auto &f = frames[i];
124
+ auto result = future.get ();
125
+ auto key =
126
+ String::NewFromUtf8 (capture_from_isolate, result.thread_name .c_str (),
127
+ NewStringType::kNormal )
128
+ .ToLocalChecked ();
129
+
130
+ Local<Array> jsFrames =
131
+ Array::New (capture_from_isolate, result.stack_frames .size ());
132
+ for (size_t i = 0 ; i < result.stack_frames .size (); ++i) {
133
+ const auto &frame = result.stack_frames [i];
124
134
Local<Object> frameObj = Object::New (capture_from_isolate);
125
135
frameObj
126
136
->Set (current_context,
127
137
String::NewFromUtf8 (capture_from_isolate, " function" ,
128
138
NewStringType::kInternalized )
129
139
.ToLocalChecked (),
130
140
String::NewFromUtf8 (capture_from_isolate,
131
- f .function_name .c_str (),
141
+ frame .function_name .c_str (),
132
142
NewStringType::kNormal )
133
143
.ToLocalChecked ())
134
144
.Check ();
@@ -137,7 +147,8 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
137
147
String::NewFromUtf8 (capture_from_isolate, " filename" ,
138
148
NewStringType::kInternalized )
139
149
.ToLocalChecked (),
140
- String::NewFromUtf8 (capture_from_isolate, f.filename .c_str (),
150
+ String::NewFromUtf8 (capture_from_isolate,
151
+ frame.filename .c_str (),
141
152
NewStringType::kNormal )
142
153
.ToLocalChecked ())
143
154
.Check ();
@@ -146,23 +157,52 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
146
157
String::NewFromUtf8 (capture_from_isolate, " lineno" ,
147
158
NewStringType::kInternalized )
148
159
.ToLocalChecked (),
149
- Integer::New (capture_from_isolate, f .lineno ))
160
+ Integer::New (capture_from_isolate, frame .lineno ))
150
161
.Check ();
151
162
frameObj
152
163
->Set (current_context,
153
164
String::NewFromUtf8 (capture_from_isolate, " colno" ,
154
165
NewStringType::kInternalized )
155
166
.ToLocalChecked (),
156
- Integer::New (capture_from_isolate, f .colno ))
167
+ Integer::New (capture_from_isolate, frame .colno ))
157
168
.Check ();
158
169
jsFrames->Set (current_context, static_cast <uint32_t >(i), frameObj)
159
170
.Check ();
160
171
}
161
172
162
- result->Set (current_context, key, jsFrames).Check ();
173
+ // Create a thread object with a 'frames' property and optional 'state'
174
+ Local<Object> threadObj = Object::New (capture_from_isolate);
175
+ threadObj
176
+ ->Set (current_context,
177
+ String::NewFromUtf8 (capture_from_isolate, " frames" ,
178
+ NewStringType::kInternalized )
179
+ .ToLocalChecked (),
180
+ jsFrames)
181
+ .Check ();
182
+
183
+ if (!result.state .empty ()) {
184
+ v8::MaybeLocal<v8::String> stateStr = v8::String::NewFromUtf8 (
185
+ capture_from_isolate, result.state .c_str (), NewStringType::kNormal );
186
+ if (!stateStr.IsEmpty ()) {
187
+ v8::MaybeLocal<v8::Value> maybeStateVal =
188
+ v8::JSON::Parse (current_context, stateStr.ToLocalChecked ());
189
+ v8::Local<v8::Value> stateVal;
190
+ if (maybeStateVal.ToLocal (&stateVal)) {
191
+ threadObj
192
+ ->Set (current_context,
193
+ String::NewFromUtf8 (capture_from_isolate, " state" ,
194
+ NewStringType::kInternalized )
195
+ .ToLocalChecked (),
196
+ stateVal)
197
+ .Check ();
198
+ }
199
+ }
200
+ }
201
+
202
+ output->Set (current_context, key, threadObj).Check ();
163
203
}
164
204
165
- args.GetReturnValue ().Set (result );
205
+ args.GetReturnValue ().Set (output );
166
206
}
167
207
168
208
// Cleanup function to remove the thread from the map when the isolate is
@@ -194,13 +234,39 @@ void RegisterThread(const FunctionCallbackInfo<Value> &args) {
194
234
std::lock_guard<std::mutex> lock (threads_mutex);
195
235
auto found = threads.find (isolate);
196
236
if (found == threads.end ()) {
197
- threads.emplace (isolate, ThreadInfo{thread_name, milliseconds::zero ()});
237
+ threads.emplace (isolate,
238
+ ThreadInfo{thread_name, milliseconds::zero (), " " });
198
239
// Register a cleanup hook to remove this thread when the isolate is
199
240
// destroyed
200
241
node::AddEnvironmentCleanupHook (isolate, Cleanup, isolate);
242
+ }
243
+ }
244
+ }
245
+
246
+ // Function to track a thread and set its state
247
+ void ThreadPoll (const FunctionCallbackInfo<Value> &args) {
248
+ auto isolate = args.GetIsolate ();
249
+ auto context = isolate->GetCurrentContext ();
250
+
251
+ std::string state_str;
252
+ if (args.Length () == 1 && args[0 ]->IsValue ()) {
253
+ MaybeLocal<String> maybe_json = v8::JSON::Stringify (context, args[0 ]);
254
+ if (!maybe_json.IsEmpty ()) {
255
+ v8::String::Utf8Value utf8_state (isolate, maybe_json.ToLocalChecked ());
256
+ state_str = *utf8_state ? *utf8_state : " " ;
201
257
} else {
258
+ state_str = " " ;
259
+ }
260
+ } else {
261
+ state_str = " " ;
262
+ }
263
+
264
+ {
265
+ std::lock_guard<std::mutex> lock (threads_mutex);
266
+ auto found = threads.find (isolate);
267
+ if (found != threads.end ()) {
202
268
auto &thread_info = found->second ;
203
- thread_info.thread_name = thread_name ;
269
+ thread_info.state = state_str ;
204
270
thread_info.last_seen =
205
271
duration_cast<milliseconds>(system_clock::now ().time_since_epoch ());
206
272
}
@@ -257,6 +323,16 @@ NODE_MODULE_INITIALIZER(Local<Object> exports, Local<Value> module,
257
323
.ToLocalChecked ())
258
324
.Check ();
259
325
326
+ exports
327
+ ->Set (context,
328
+ String::NewFromUtf8 (isolate, " threadPoll" ,
329
+ NewStringType::kInternalized )
330
+ .ToLocalChecked (),
331
+ FunctionTemplate::New (isolate, ThreadPoll)
332
+ ->GetFunction (context)
333
+ .ToLocalChecked ())
334
+ .Check ();
335
+
260
336
exports
261
337
->Set (context,
262
338
String::NewFromUtf8 (isolate, " getThreadsLastSeen" ,
0 commit comments