/src/node/src/node_worker.cc
Line | Count | Source |
1 | | #include "node_worker.h" |
2 | | #include "async_wrap-inl.h" |
3 | | #include "debug_utils-inl.h" |
4 | | #include "histogram-inl.h" |
5 | | #include "memory_tracker-inl.h" |
6 | | #include "node_buffer.h" |
7 | | #include "node_errors.h" |
8 | | #include "node_external_reference.h" |
9 | | #include "node_options-inl.h" |
10 | | #include "node_perf.h" |
11 | | #include "node_snapshot_builder.h" |
12 | | #include "permission/permission.h" |
13 | | #include "util-inl.h" |
14 | | #include "v8-cppgc.h" |
15 | | #include "v8-profiler.h" |
16 | | |
17 | | #include <memory> |
18 | | #include <string> |
19 | | #include <vector> |
20 | | |
21 | | using node::kAllowedInEnvvar; |
22 | | using node::kDisallowedInEnvvar; |
23 | | using v8::AllocationProfile; |
24 | | using v8::Array; |
25 | | using v8::ArrayBuffer; |
26 | | using v8::Boolean; |
27 | | using v8::Context; |
28 | | using v8::CpuProfile; |
29 | | using v8::CpuProfilingResult; |
30 | | using v8::CpuProfilingStatus; |
31 | | using v8::DictionaryTemplate; |
32 | | using v8::Float64Array; |
33 | | using v8::FunctionCallbackInfo; |
34 | | using v8::FunctionTemplate; |
35 | | using v8::HandleScope; |
36 | | using v8::HeapProfiler; |
37 | | using v8::HeapStatistics; |
38 | | using v8::Integer; |
39 | | using v8::Isolate; |
40 | | using v8::Local; |
41 | | using v8::Locker; |
42 | | using v8::Maybe; |
43 | | using v8::MaybeLocal; |
44 | | using v8::NewStringType; |
45 | | using v8::Null; |
46 | | using v8::Number; |
47 | | using v8::Object; |
48 | | using v8::ObjectTemplate; |
49 | | using v8::ResourceConstraints; |
50 | | using v8::SealHandleScope; |
51 | | using v8::String; |
52 | | using v8::TryCatch; |
53 | | using v8::Value; |
54 | | |
55 | | namespace node { |
56 | | namespace worker { |
57 | | |
58 | | constexpr double kMB = 1024 * 1024; |
59 | | |
60 | | Worker::Worker(Environment* env, |
61 | | Local<Object> wrap, |
62 | | const std::string& url, |
63 | | const std::string& name, |
64 | | std::shared_ptr<PerIsolateOptions> per_isolate_opts, |
65 | | std::vector<std::string>&& exec_argv, |
66 | | std::shared_ptr<KVStore> env_vars, |
67 | | const SnapshotData* snapshot_data, |
68 | | const bool is_internal) |
69 | 0 | : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER), |
70 | 0 | per_isolate_opts_(per_isolate_opts), |
71 | 0 | exec_argv_(exec_argv), |
72 | 0 | platform_(env->isolate_data()->platform()), |
73 | 0 | thread_id_(AllocateEnvironmentThreadId()), |
74 | 0 | name_(name), |
75 | 0 | env_vars_(env_vars), |
76 | 0 | embedder_preload_(env->embedder_preload()), |
77 | 0 | snapshot_data_(snapshot_data), |
78 | 0 | is_internal_(is_internal) { |
79 | 0 | Debug(this, "Creating new worker instance with thread id %llu", |
80 | 0 | thread_id_.id); |
81 | | |
82 | | // Set up everything that needs to be set up in the parent environment. |
83 | 0 | MessagePort* parent_port = MessagePort::New(env, env->context()); |
84 | 0 | if (parent_port == nullptr) { |
85 | | // This can happen e.g. because execution is terminating. |
86 | 0 | return; |
87 | 0 | } |
88 | | |
89 | 0 | child_port_data_ = std::make_unique<MessagePortData>(nullptr); |
90 | 0 | MessagePort::Entangle(parent_port, child_port_data_.get()); |
91 | |
|
92 | 0 | object() |
93 | 0 | ->Set(env->context(), env->message_port_string(), parent_port->object()) |
94 | 0 | .Check(); |
95 | |
|
96 | 0 | object()->Set(env->context(), |
97 | 0 | env->thread_id_string(), |
98 | 0 | Number::New(env->isolate(), static_cast<double>(thread_id_.id))) |
99 | 0 | .Check(); |
100 | |
|
101 | 0 | object() |
102 | 0 | ->Set(env->context(), |
103 | 0 | env->thread_name_string(), |
104 | 0 | String::NewFromUtf8(env->isolate(), |
105 | 0 | name_.data(), |
106 | 0 | NewStringType::kNormal, |
107 | 0 | name_.size()) |
108 | 0 | .ToLocalChecked()) |
109 | 0 | .Check(); |
110 | | // Without this check, to use the permission model with |
111 | | // workers (--allow-worker) one would need to pass --allow-inspector as well |
112 | 0 | if (env->permission()->is_granted( |
113 | 0 | env, node::permission::PermissionScope::kInspector)) { |
114 | 0 | inspector_parent_handle_ = |
115 | 0 | GetInspectorParentHandle(env, thread_id_, url, name); |
116 | 0 | } |
117 | |
|
118 | 0 | argv_ = std::vector<std::string>{env->argv()[0]}; |
119 | | // Mark this Worker object as weak until we actually start the thread. |
120 | 0 | MakeWeak(); |
121 | |
|
122 | 0 | Debug(this, "Preparation for worker %llu finished", thread_id_.id); |
123 | 0 | } |
124 | | |
125 | 0 | bool Worker::is_stopped() const { |
126 | 0 | Mutex::ScopedLock lock(mutex_); |
127 | 0 | if (env_ != nullptr) |
128 | 0 | return env_->is_stopping(); |
129 | 0 | return stopped_; |
130 | 0 | } |
131 | | |
132 | 0 | void Worker::UpdateResourceConstraints(ResourceConstraints* constraints) { |
133 | 0 | constraints->set_stack_limit(reinterpret_cast<uint32_t*>(stack_base_)); |
134 | |
|
135 | 0 | if (resource_limits_[kMaxYoungGenerationSizeMb] > 0) { |
136 | 0 | constraints->set_max_young_generation_size_in_bytes( |
137 | 0 | static_cast<size_t>(resource_limits_[kMaxYoungGenerationSizeMb] * kMB)); |
138 | 0 | } else { |
139 | 0 | resource_limits_[kMaxYoungGenerationSizeMb] = |
140 | 0 | constraints->max_young_generation_size_in_bytes() / kMB; |
141 | 0 | } |
142 | |
|
143 | 0 | if (resource_limits_[kMaxOldGenerationSizeMb] > 0) { |
144 | 0 | constraints->set_max_old_generation_size_in_bytes( |
145 | 0 | static_cast<size_t>(resource_limits_[kMaxOldGenerationSizeMb] * kMB)); |
146 | 0 | } else { |
147 | 0 | resource_limits_[kMaxOldGenerationSizeMb] = |
148 | 0 | constraints->max_old_generation_size_in_bytes() / kMB; |
149 | 0 | } |
150 | |
|
151 | 0 | if (resource_limits_[kCodeRangeSizeMb] > 0) { |
152 | 0 | constraints->set_code_range_size_in_bytes( |
153 | 0 | static_cast<size_t>(resource_limits_[kCodeRangeSizeMb] * kMB)); |
154 | 0 | } else { |
155 | 0 | resource_limits_[kCodeRangeSizeMb] = |
156 | 0 | constraints->code_range_size_in_bytes() / kMB; |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | // This class contains data that is only relevant to the child thread itself, |
161 | | // and only while it is running. |
162 | | // (Eventually, the Environment instance should probably also be moved here.) |
163 | | class WorkerThreadData { |
164 | | public: |
165 | | explicit WorkerThreadData(Worker* w) |
166 | 0 | : w_(w) { |
167 | 0 | int ret = uv_loop_init(&loop_); |
168 | 0 | if (ret != 0) { |
169 | 0 | char err_buf[128]; |
170 | 0 | uv_err_name_r(ret, err_buf, sizeof(err_buf)); |
171 | | // TODO(joyeecheung): maybe this should be kBootstrapFailure instead? |
172 | 0 | w->Exit(ExitCode::kGenericUserError, "ERR_WORKER_INIT_FAILED", err_buf); |
173 | 0 | return; |
174 | 0 | } |
175 | 0 | loop_init_failed_ = false; |
176 | 0 | uv_loop_configure(&loop_, UV_METRICS_IDLE_TIME); |
177 | |
|
178 | 0 | std::shared_ptr<ArrayBufferAllocator> allocator = |
179 | 0 | ArrayBufferAllocator::Create(); |
180 | 0 | Isolate::CreateParams params; |
181 | 0 | SetIsolateCreateParamsForNode(¶ms); |
182 | 0 | w->UpdateResourceConstraints(¶ms.constraints); |
183 | 0 | params.array_buffer_allocator_shared = allocator; |
184 | 0 | Isolate* isolate = |
185 | 0 | NewIsolate(¶ms, &loop_, w->platform_, w->snapshot_data()); |
186 | 0 | if (isolate == nullptr) { |
187 | | // TODO(joyeecheung): maybe this should be kBootstrapFailure instead? |
188 | 0 | w->Exit(ExitCode::kGenericUserError, |
189 | 0 | "ERR_WORKER_INIT_FAILED", |
190 | 0 | "Failed to create new Isolate"); |
191 | 0 | return; |
192 | 0 | } |
193 | | |
194 | 0 | SetIsolateUpForNode(isolate); |
195 | | |
196 | | // Be sure it's called before Environment::InitializeDiagnostics() |
197 | | // so that this callback stays when the callback of |
198 | | // --heapsnapshot-near-heap-limit gets is popped. |
199 | 0 | isolate->AddNearHeapLimitCallback(Worker::NearHeapLimit, w); |
200 | |
|
201 | 0 | { |
202 | 0 | Locker locker(isolate); |
203 | 0 | Isolate::Scope isolate_scope(isolate); |
204 | | // V8 computes its stack limit the first time a `Locker` is used based on |
205 | | // --stack-size. Reset it to the correct value. |
206 | 0 | isolate->SetStackLimit(w->stack_base_); |
207 | |
|
208 | 0 | HandleScope handle_scope(isolate); |
209 | 0 | isolate_data_.reset(IsolateData::CreateIsolateData( |
210 | 0 | isolate, |
211 | 0 | &loop_, |
212 | 0 | w_->platform_, |
213 | 0 | allocator.get(), |
214 | 0 | w->snapshot_data()->AsEmbedderWrapper().get(), |
215 | 0 | std::move(w_->per_isolate_opts_))); |
216 | 0 | CHECK(isolate_data_); |
217 | 0 | CHECK(!isolate_data_->is_building_snapshot()); |
218 | 0 | isolate_data_->set_worker_context(w_); |
219 | 0 | isolate_data_->max_young_gen_size = |
220 | 0 | params.constraints.max_young_generation_size_in_bytes(); |
221 | 0 | } |
222 | | |
223 | 0 | Mutex::ScopedLock lock(w_->mutex_); |
224 | 0 | w_->isolate_ = isolate; |
225 | 0 | } |
226 | | |
227 | 0 | ~WorkerThreadData() { |
228 | 0 | Debug(w_, "Worker %llu dispose isolate", w_->thread_id_.id); |
229 | 0 | Isolate* isolate; |
230 | 0 | { |
231 | 0 | Mutex::ScopedLock lock(w_->mutex_); |
232 | 0 | isolate = w_->isolate_; |
233 | 0 | w_->isolate_ = nullptr; |
234 | 0 | } |
235 | |
|
236 | 0 | if (isolate != nullptr) { |
237 | 0 | CHECK(!loop_init_failed_); |
238 | 0 | bool platform_finished = false; |
239 | | |
240 | | // https://github.com/nodejs/node/issues/51129 - IsolateData destructor |
241 | | // can kick off GC before teardown, so ensure the isolate is entered. |
242 | 0 | { |
243 | 0 | Locker locker(isolate); |
244 | 0 | Isolate::Scope isolate_scope(isolate); |
245 | 0 | isolate_data_.reset(); |
246 | 0 | } |
247 | |
|
248 | 0 | w_->platform_->AddIsolateFinishedCallback(isolate, [](void* data) { |
249 | 0 | *static_cast<bool*>(data) = true; |
250 | 0 | }, &platform_finished); |
251 | |
|
252 | 0 | w_->platform_->DisposeIsolate(isolate); |
253 | | |
254 | | // Wait until the platform has cleaned up all relevant resources. |
255 | 0 | while (!platform_finished) { |
256 | 0 | uv_run(&loop_, UV_RUN_ONCE); |
257 | 0 | } |
258 | 0 | } |
259 | 0 | if (!loop_init_failed_) { |
260 | 0 | CheckedUvLoopClose(&loop_); |
261 | 0 | } |
262 | 0 | } |
263 | | |
264 | 0 | bool loop_is_usable() const { return !loop_init_failed_; } |
265 | | |
266 | | private: |
267 | | Worker* const w_; |
268 | | uv_loop_t loop_; |
269 | | bool loop_init_failed_ = true; |
270 | | DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data_; |
271 | | friend class Worker; |
272 | | }; |
273 | | |
274 | | size_t Worker::NearHeapLimit(void* data, size_t current_heap_limit, |
275 | 0 | size_t initial_heap_limit) { |
276 | 0 | Worker* worker = static_cast<Worker*>(data); |
277 | | // Give the current GC some extra leeway to let it finish rather than |
278 | | // crash hard. We are not going to perform further allocations anyway. |
279 | 0 | constexpr size_t kExtraHeapAllowance = 16 * 1024 * 1024; |
280 | 0 | size_t new_limit = current_heap_limit + kExtraHeapAllowance; |
281 | 0 | Environment* env = worker->env(); |
282 | 0 | if (env != nullptr) { |
283 | 0 | DCHECK(!env->is_in_heapsnapshot_heap_limit_callback()); |
284 | 0 | Debug(env, |
285 | 0 | DebugCategory::DIAGNOSTICS, |
286 | 0 | "Throwing ERR_WORKER_OUT_OF_MEMORY, " |
287 | 0 | "new_limit=%" PRIu64 "\n", |
288 | 0 | static_cast<uint64_t>(new_limit)); |
289 | 0 | } |
290 | | // TODO(joyeecheung): maybe this should be kV8FatalError instead? |
291 | 0 | worker->Exit(ExitCode::kGenericUserError, |
292 | 0 | "ERR_WORKER_OUT_OF_MEMORY", |
293 | 0 | "JS heap out of memory"); |
294 | 0 | return new_limit; |
295 | 0 | } |
296 | | |
297 | 0 | void Worker::Run() { |
298 | 0 | std::string trace_name = "[worker " + std::to_string(thread_id_.id) + "]" + |
299 | 0 | (name_ == "" ? "" : " " + name_); |
300 | 0 | TRACE_EVENT_METADATA1( |
301 | 0 | "__metadata", "thread_name", "name", TRACE_STR_COPY(trace_name.c_str())); |
302 | 0 | CHECK_NOT_NULL(platform_); |
303 | | |
304 | 0 | Debug(this, "Creating isolate for worker with id %llu", thread_id_.id); |
305 | |
|
306 | 0 | WorkerThreadData data(this); |
307 | 0 | if (isolate_ == nullptr) return; |
308 | 0 | CHECK(data.loop_is_usable()); |
309 | | |
310 | 0 | Debug(this, "Starting worker with id %llu", thread_id_.id); |
311 | 0 | { |
312 | 0 | Locker locker(isolate_); |
313 | 0 | Isolate::Scope isolate_scope(isolate_); |
314 | 0 | SealHandleScope outer_seal(isolate_); |
315 | |
|
316 | 0 | DeleteFnPtr<Environment, FreeEnvironment> env_; |
317 | 0 | auto cleanup_env = OnScopeLeave([&]() { |
318 | | // TODO(addaleax): This call is harmless but should not be necessary. |
319 | | // Figure out why V8 is raising a DCHECK() here without it |
320 | | // (in test/parallel/test-async-hooks-worker-asyncfn-terminate-4.js). |
321 | 0 | isolate_->CancelTerminateExecution(); |
322 | |
|
323 | 0 | if (!env_) return; |
324 | 0 | env_->set_can_call_into_js(false); |
325 | |
|
326 | 0 | { |
327 | 0 | Mutex::ScopedLock lock(mutex_); |
328 | 0 | stopped_ = true; |
329 | 0 | this->env_ = nullptr; |
330 | 0 | } |
331 | |
|
332 | 0 | env_.reset(); |
333 | 0 | }); |
334 | |
|
335 | 0 | if (is_stopped()) return; |
336 | 0 | { |
337 | 0 | HandleScope handle_scope(isolate_); |
338 | 0 | Local<Context> context; |
339 | 0 | { |
340 | | // We create the Context object before we have an Environment* in place |
341 | | // that we could use for error handling. If creation fails due to |
342 | | // resource constraints, we need something in place to handle it, |
343 | | // though. |
344 | 0 | TryCatch try_catch(isolate_); |
345 | 0 | if (snapshot_data_ != nullptr) { |
346 | 0 | Debug(this, |
347 | 0 | "Worker %llu uses context from snapshot %d\n", |
348 | 0 | thread_id_.id, |
349 | 0 | static_cast<int>(SnapshotData::kNodeBaseContextIndex)); |
350 | 0 | context = Context::FromSnapshot(isolate_, |
351 | 0 | SnapshotData::kNodeBaseContextIndex) |
352 | 0 | .ToLocalChecked(); |
353 | 0 | if (!context.IsEmpty() && |
354 | 0 | !InitializeContextRuntime(context).IsJust()) { |
355 | 0 | context = Local<Context>(); |
356 | 0 | } |
357 | 0 | } else { |
358 | 0 | Debug( |
359 | 0 | this, "Worker %llu builds context from scratch\n", thread_id_.id); |
360 | 0 | context = NewContext(isolate_); |
361 | 0 | } |
362 | 0 | if (context.IsEmpty()) { |
363 | | // TODO(joyeecheung): maybe this should be kBootstrapFailure instead? |
364 | 0 | Exit(ExitCode::kGenericUserError, |
365 | 0 | "ERR_WORKER_INIT_FAILED", |
366 | 0 | "Failed to create new Context"); |
367 | 0 | return; |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | 0 | if (is_stopped()) return; |
372 | 0 | CHECK(!context.IsEmpty()); |
373 | 0 | Context::Scope context_scope(context); |
374 | 0 | { |
375 | 0 | #if HAVE_INSPECTOR |
376 | 0 | environment_flags_ |= EnvironmentFlags::kNoWaitForInspectorFrontend; |
377 | 0 | #endif |
378 | 0 | env_.reset(CreateEnvironment( |
379 | 0 | data.isolate_data_.get(), |
380 | 0 | context, |
381 | 0 | std::move(argv_), |
382 | 0 | std::move(exec_argv_), |
383 | 0 | static_cast<EnvironmentFlags::Flags>(environment_flags_), |
384 | 0 | thread_id_, |
385 | 0 | std::move(inspector_parent_handle_), |
386 | 0 | name_)); |
387 | 0 | if (is_stopped()) return; |
388 | 0 | CHECK_NOT_NULL(env_); |
389 | 0 | env_->set_env_vars(std::move(env_vars_)); |
390 | 0 | SetProcessExitHandler(env_.get(), [this](Environment*, int exit_code) { |
391 | 0 | Exit(static_cast<ExitCode>(exit_code)); |
392 | 0 | }); |
393 | 0 | } |
394 | 0 | { |
395 | 0 | Mutex::ScopedLock lock(mutex_); |
396 | 0 | if (stopped_) return; |
397 | 0 | this->env_ = env_.get(); |
398 | 0 | } |
399 | 0 | Debug(this, "Created Environment for worker with id %llu", thread_id_.id); |
400 | |
|
401 | 0 | #if HAVE_INSPECTOR |
402 | 0 | this->env_->WaitForInspectorFrontendByOptions(); |
403 | 0 | #endif |
404 | 0 | if (is_stopped()) return; |
405 | 0 | { |
406 | 0 | if (!CreateEnvMessagePort(env_.get())) { |
407 | 0 | return; |
408 | 0 | } |
409 | | |
410 | 0 | Debug(this, "Created message port for worker %llu", thread_id_.id); |
411 | 0 | if (LoadEnvironment(env_.get(), |
412 | 0 | StartExecutionCallback{}, |
413 | 0 | std::move(embedder_preload_)) |
414 | 0 | .IsEmpty()) { |
415 | 0 | return; |
416 | 0 | } |
417 | | |
418 | 0 | Debug(this, "Loaded environment for worker %llu", thread_id_.id); |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | 0 | { |
423 | 0 | Maybe<ExitCode> exit_code = SpinEventLoopInternal(env_.get()); |
424 | 0 | Mutex::ScopedLock lock(mutex_); |
425 | 0 | if (exit_code_ == ExitCode::kNoFailure && exit_code.IsJust()) { |
426 | 0 | exit_code_ = exit_code.FromJust(); |
427 | 0 | } |
428 | |
|
429 | 0 | Debug(this, |
430 | 0 | "Exiting thread for worker %llu with exit code %d", |
431 | 0 | thread_id_.id, |
432 | 0 | static_cast<int>(exit_code_)); |
433 | 0 | } |
434 | 0 | } |
435 | | |
436 | 0 | Debug(this, "Worker %llu thread stops", thread_id_.id); |
437 | 0 | } |
438 | | |
439 | 0 | bool Worker::CreateEnvMessagePort(Environment* env) { |
440 | 0 | HandleScope handle_scope(isolate_); |
441 | 0 | std::unique_ptr<MessagePortData> data; |
442 | 0 | { |
443 | 0 | Mutex::ScopedLock lock(mutex_); |
444 | 0 | data = std::move(child_port_data_); |
445 | 0 | } |
446 | | |
447 | | // Set up the message channel for receiving messages in the child. |
448 | 0 | MessagePort* child_port = MessagePort::New(env, |
449 | 0 | env->context(), |
450 | 0 | std::move(data)); |
451 | | // MessagePort::New() may return nullptr if execution is terminated |
452 | | // within it. |
453 | 0 | if (child_port != nullptr) |
454 | 0 | env->set_message_port(child_port->object(isolate_)); |
455 | |
|
456 | 0 | return child_port; |
457 | 0 | } |
458 | | |
459 | 0 | void Worker::JoinThread() { |
460 | 0 | if (!tid_.has_value()) |
461 | 0 | return; |
462 | 0 | CHECK_EQ(uv_thread_join(&tid_.value()), 0); |
463 | 0 | tid_.reset(); |
464 | |
|
465 | 0 | env()->remove_sub_worker_context(this); |
466 | | |
467 | | // Join may happen after the worker exits and disposes the isolate |
468 | 0 | if (!env()->can_call_into_js()) return; |
469 | | |
470 | 0 | { |
471 | 0 | HandleScope handle_scope(env()->isolate()); |
472 | 0 | Context::Scope context_scope(env()->context()); |
473 | | |
474 | | // Reset the parent port as we're closing it now anyway. |
475 | 0 | object()->Set(env()->context(), |
476 | 0 | env()->message_port_string(), |
477 | 0 | Undefined(env()->isolate())).Check(); |
478 | |
|
479 | 0 | Local<Value> args[] = { |
480 | 0 | Integer::New(env()->isolate(), static_cast<int>(exit_code_)), |
481 | 0 | custom_error_ != nullptr |
482 | 0 | ? OneByteString(env()->isolate(), custom_error_).As<Value>() |
483 | 0 | : Null(env()->isolate()).As<Value>(), |
484 | 0 | !custom_error_str_.empty() |
485 | 0 | ? OneByteString(env()->isolate(), custom_error_str_.c_str()) |
486 | 0 | .As<Value>() |
487 | 0 | : Null(env()->isolate()).As<Value>(), |
488 | 0 | }; |
489 | |
|
490 | 0 | MakeCallback(env()->onexit_string(), arraysize(args), args); |
491 | 0 | } |
492 | | |
493 | | // If we get here, the tid_.has_value() condition at the top of the function |
494 | | // implies that the thread was running. In that case, its final action will |
495 | | // be to schedule a callback on the parent thread which will delete this |
496 | | // object, so there's nothing more to do here. |
497 | 0 | } |
498 | | |
499 | 0 | Worker::~Worker() { |
500 | 0 | Mutex::ScopedLock lock(mutex_); |
501 | |
|
502 | 0 | CHECK(stopped_); |
503 | 0 | CHECK_NULL(env_); |
504 | 0 | CHECK(!tid_.has_value()); |
505 | 0 | Debug(this, "Worker %llu destroyed", thread_id_.id); |
506 | 0 | } |
507 | | |
508 | 0 | void Worker::New(const FunctionCallbackInfo<Value>& args) { |
509 | 0 | Environment* env = Environment::GetCurrent(args); |
510 | 0 | THROW_IF_INSUFFICIENT_PERMISSIONS( |
511 | 0 | env, permission::PermissionScope::kWorkerThreads, ""); |
512 | 0 | bool is_internal = args[5]->IsTrue(); |
513 | 0 | Isolate* isolate = args.GetIsolate(); |
514 | |
|
515 | 0 | CHECK(args.IsConstructCall()); |
516 | | |
517 | 0 | if (env->isolate_data()->platform() == nullptr) { |
518 | 0 | THROW_ERR_MISSING_PLATFORM_FOR_WORKER(env); |
519 | 0 | return; |
520 | 0 | } |
521 | 0 | CHECK(!env->isolate_data()->is_building_snapshot()); |
522 | | |
523 | 0 | std::string url; |
524 | 0 | std::string name; |
525 | 0 | std::shared_ptr<PerIsolateOptions> per_isolate_opts = nullptr; |
526 | 0 | std::shared_ptr<KVStore> env_vars = nullptr; |
527 | |
|
528 | 0 | std::vector<std::string> exec_argv_out; |
529 | | |
530 | | // Argument might be a string or URL |
531 | 0 | if (!args[0]->IsNullOrUndefined()) { |
532 | 0 | Utf8Value value( |
533 | 0 | isolate, args[0]->ToString(env->context()).FromMaybe(Local<String>())); |
534 | 0 | url.append(value.out(), value.length()); |
535 | 0 | } |
536 | |
|
537 | 0 | if (!args[6]->IsNullOrUndefined()) { |
538 | 0 | Utf8Value value( |
539 | 0 | isolate, args[6]->ToString(env->context()).FromMaybe(Local<String>())); |
540 | 0 | name.append(value.out(), value.length()); |
541 | 0 | } |
542 | |
|
543 | 0 | if (args[1]->IsNull()) { |
544 | | // Means worker.env = { ...process.env }. |
545 | 0 | env_vars = env->env_vars()->Clone(isolate); |
546 | 0 | } else if (args[1]->IsObject()) { |
547 | | // User provided env. |
548 | 0 | env_vars = KVStore::CreateMapKVStore(); |
549 | 0 | if (env_vars |
550 | 0 | ->AssignFromObject(isolate->GetCurrentContext(), |
551 | 0 | args[1].As<Object>()) |
552 | 0 | .IsNothing()) { |
553 | 0 | return; |
554 | 0 | } |
555 | 0 | } else { |
556 | | // Env is shared. |
557 | 0 | env_vars = env->env_vars(); |
558 | 0 | } |
559 | | |
560 | 0 | if (!env_vars) { |
561 | 0 | THROW_ERR_OPERATION_FAILED(env, "Failed to copy environment variables"); |
562 | 0 | } |
563 | |
|
564 | 0 | if (args[1]->IsObject() || args[2]->IsArray()) { |
565 | 0 | per_isolate_opts.reset(new PerIsolateOptions()); |
566 | |
|
567 | 0 | HandleEnvOptions(per_isolate_opts->per_env, [&env_vars](const char* name) { |
568 | 0 | return env_vars->Get(name).value_or(""); |
569 | 0 | }); |
570 | |
|
571 | 0 | #ifndef NODE_WITHOUT_NODE_OPTIONS |
572 | 0 | std::optional<std::string> node_options = env_vars->Get("NODE_OPTIONS"); |
573 | 0 | if (node_options.has_value()) { |
574 | 0 | std::vector<std::string> errors{}; |
575 | 0 | std::vector<std::string> env_argv = |
576 | 0 | ParseNodeOptionsEnvVar(node_options.value(), &errors); |
577 | | // [0] is expected to be the program name, add dummy string. |
578 | 0 | env_argv.insert(env_argv.begin(), ""); |
579 | 0 | std::vector<std::string> invalid_args{}; |
580 | |
|
581 | 0 | std::optional<std::string> parent_node_options = |
582 | 0 | env->env_vars()->Get("NODE_OPTIONS"); |
583 | | |
584 | | // If the worker code passes { env: { ...process.env, ... } } or |
585 | | // the NODE_OPTIONS is otherwise character-for-character equal to the |
586 | | // original NODE_OPTIONS, allow per-process options inherited into |
587 | | // the worker since worker spawning code is not usually in charge of |
588 | | // how the NODE_OPTIONS is configured for the parent. |
589 | | // TODO(joyeecheung): a more intelligent filter may be more desirable. |
590 | | // but a string comparison is good enough(TM) for the case where the |
591 | | // worker spawning code just wants to pass the parent configuration down |
592 | | // and does not intend to modify NODE_OPTIONS. |
593 | 0 | if (parent_node_options == node_options) { |
594 | | // Creates a wrapper per-process option over the per_isolate_opts |
595 | | // to allow per-process options copied from the parent. |
596 | 0 | std::unique_ptr<PerProcessOptions> per_process_opts = |
597 | 0 | std::make_unique<PerProcessOptions>(); |
598 | 0 | per_process_opts->per_isolate = per_isolate_opts; |
599 | 0 | options_parser::Parse(&env_argv, |
600 | 0 | nullptr, |
601 | 0 | &invalid_args, |
602 | 0 | per_process_opts.get(), |
603 | 0 | kAllowedInEnvvar, |
604 | 0 | &errors); |
605 | 0 | } else { |
606 | 0 | options_parser::Parse(&env_argv, |
607 | 0 | nullptr, |
608 | 0 | &invalid_args, |
609 | 0 | per_isolate_opts.get(), |
610 | 0 | kAllowedInEnvvar, |
611 | 0 | &errors); |
612 | 0 | } |
613 | |
|
614 | 0 | if (!errors.empty() && args[1]->IsObject()) { |
615 | | // Only fail for explicitly provided env, this protects from failures |
616 | | // when NODE_OPTIONS from parent's env is used (which is the default). |
617 | 0 | Local<Value> error; |
618 | 0 | if (!ToV8Value(env->context(), errors).ToLocal(&error)) return; |
619 | 0 | Local<String> key = |
620 | 0 | FIXED_ONE_BYTE_STRING(env->isolate(), "invalidNodeOptions"); |
621 | | // Ignore the return value of Set() because exceptions bubble up to JS |
622 | | // when we return anyway. |
623 | 0 | USE(args.This()->Set(env->context(), key, error)); |
624 | 0 | return; |
625 | 0 | } |
626 | 0 | } |
627 | 0 | #endif // NODE_WITHOUT_NODE_OPTIONS |
628 | | |
629 | | // The first argument is reserved for program name, but we don't need it |
630 | | // in workers. |
631 | 0 | std::vector<std::string> exec_argv = {""}; |
632 | 0 | if (args[2]->IsArray()) { |
633 | 0 | Local<Array> array = args[2].As<Array>(); |
634 | 0 | uint32_t length = array->Length(); |
635 | 0 | for (uint32_t i = 0; i < length; i++) { |
636 | 0 | Local<Value> arg; |
637 | 0 | if (!array->Get(env->context(), i).ToLocal(&arg)) { |
638 | 0 | return; |
639 | 0 | } |
640 | 0 | Local<String> arg_v8; |
641 | 0 | if (!arg->ToString(env->context()).ToLocal(&arg_v8)) { |
642 | 0 | return; |
643 | 0 | } |
644 | 0 | Utf8Value arg_utf8_value(args.GetIsolate(), arg_v8); |
645 | 0 | std::string arg_string(arg_utf8_value.out(), arg_utf8_value.length()); |
646 | 0 | exec_argv.push_back(arg_string); |
647 | 0 | } |
648 | 0 | } else { |
649 | 0 | exec_argv.insert( |
650 | 0 | exec_argv.end(), env->exec_argv().begin(), env->exec_argv().end()); |
651 | 0 | } |
652 | | |
653 | 0 | std::vector<std::string> invalid_args{}; |
654 | 0 | std::vector<std::string> errors{}; |
655 | | // Using invalid_args as the v8_args argument as it stores unknown |
656 | | // options for the per isolate parser. |
657 | 0 | options_parser::Parse(&exec_argv, |
658 | 0 | &exec_argv_out, |
659 | 0 | &invalid_args, |
660 | 0 | per_isolate_opts.get(), |
661 | 0 | kDisallowedInEnvvar, |
662 | 0 | &errors); |
663 | | |
664 | | // The first argument is program name. |
665 | 0 | invalid_args.erase(invalid_args.begin()); |
666 | | // Only fail for explicitly provided execArgv, this protects from failures |
667 | | // when execArgv from parent's execArgv is used (which is the default). |
668 | 0 | if (errors.size() > 0 || (invalid_args.size() > 0 && args[2]->IsArray())) { |
669 | 0 | Local<Value> error; |
670 | 0 | if (!ToV8Value(env->context(), errors.size() > 0 ? errors : invalid_args) |
671 | 0 | .ToLocal(&error)) { |
672 | 0 | return; |
673 | 0 | } |
674 | 0 | Local<String> key = |
675 | 0 | FIXED_ONE_BYTE_STRING(env->isolate(), "invalidExecArgv"); |
676 | | // Ignore the return value of Set() because exceptions bubble up to JS |
677 | | // when we return anyway. |
678 | 0 | USE(args.This()->Set(env->context(), key, error)); |
679 | 0 | return; |
680 | 0 | } |
681 | 0 | } else { |
682 | | // Copy the parent's execArgv. |
683 | 0 | exec_argv_out = env->exec_argv(); |
684 | 0 | per_isolate_opts = env->isolate_data()->options()->Clone(); |
685 | 0 | } |
686 | | |
687 | | // Internal workers should not wait for inspector frontend to connect or |
688 | | // break on the first line of internal scripts. Module loader threads are |
689 | | // essential to load user codes and must not be blocked by the inspector |
690 | | // for internal scripts. |
691 | | // Still, `--inspect-node` can break on the first line of internal scripts. |
692 | 0 | if (is_internal) { |
693 | 0 | per_isolate_opts->per_env->get_debug_options() |
694 | 0 | ->DisableWaitOrBreakFirstLine(); |
695 | 0 | } |
696 | |
|
697 | 0 | const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data(); |
698 | |
|
699 | 0 | Worker* worker = new Worker(env, |
700 | 0 | args.This(), |
701 | 0 | url, |
702 | 0 | name, |
703 | 0 | per_isolate_opts, |
704 | 0 | std::move(exec_argv_out), |
705 | 0 | env_vars, |
706 | 0 | snapshot_data, |
707 | 0 | is_internal); |
708 | |
|
709 | 0 | CHECK(args[3]->IsFloat64Array()); |
710 | 0 | Local<Float64Array> limit_info = args[3].As<Float64Array>(); |
711 | 0 | CHECK_EQ(limit_info->Length(), kTotalResourceLimitCount); |
712 | 0 | limit_info->CopyContents(worker->resource_limits_, |
713 | 0 | sizeof(worker->resource_limits_)); |
714 | |
|
715 | 0 | CHECK(args[4]->IsBoolean()); |
716 | 0 | if (args[4]->IsTrue() || env->tracks_unmanaged_fds()) |
717 | 0 | worker->environment_flags_ |= EnvironmentFlags::kTrackUnmanagedFds; |
718 | 0 | if (env->hide_console_windows()) |
719 | 0 | worker->environment_flags_ |= EnvironmentFlags::kHideConsoleWindows; |
720 | 0 | if (env->no_native_addons()) |
721 | 0 | worker->environment_flags_ |= EnvironmentFlags::kNoNativeAddons; |
722 | 0 | if (env->no_global_search_paths()) |
723 | 0 | worker->environment_flags_ |= EnvironmentFlags::kNoGlobalSearchPaths; |
724 | 0 | if (env->no_browser_globals()) |
725 | 0 | worker->environment_flags_ |= EnvironmentFlags::kNoBrowserGlobals; |
726 | 0 | } |
727 | | |
728 | 0 | void Worker::StartThread(const FunctionCallbackInfo<Value>& args) { |
729 | 0 | Worker* w; |
730 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
731 | 0 | Mutex::ScopedLock lock(w->mutex_); |
732 | |
|
733 | 0 | w->stopped_ = false; |
734 | |
|
735 | 0 | if (w->resource_limits_[kStackSizeMb] > 0) { |
736 | 0 | if (w->resource_limits_[kStackSizeMb] * kMB < kStackBufferSize) { |
737 | 0 | w->resource_limits_[kStackSizeMb] = kStackBufferSize / kMB; |
738 | 0 | w->stack_size_ = kStackBufferSize; |
739 | 0 | } else { |
740 | 0 | w->stack_size_ = |
741 | 0 | static_cast<size_t>(w->resource_limits_[kStackSizeMb] * kMB); |
742 | 0 | } |
743 | 0 | } else { |
744 | 0 | w->resource_limits_[kStackSizeMb] = w->stack_size_ / kMB; |
745 | 0 | } |
746 | |
|
747 | 0 | uv_thread_options_t thread_options; |
748 | 0 | thread_options.flags = UV_THREAD_HAS_STACK_SIZE; |
749 | 0 | thread_options.stack_size = w->stack_size_; |
750 | |
|
751 | 0 | uv_thread_t* tid = &w->tid_.emplace(); // Create uv_thread_t instance |
752 | 0 | int ret = uv_thread_create_ex(tid, &thread_options, [](void* arg) { |
753 | | // XXX: This could become a std::unique_ptr, but that makes at least |
754 | | // gcc 6.3 detect undefined behaviour when there shouldn't be any. |
755 | | // gcc 7+ handles this well. |
756 | 0 | Worker* w = static_cast<Worker*>(arg); |
757 | 0 | const uintptr_t stack_top = reinterpret_cast<uintptr_t>(&arg); |
758 | |
|
759 | 0 | uv_thread_setname(w->name_.c_str()); |
760 | | // Leave a few kilobytes just to make sure we're within limits and have |
761 | | // some space to do work in C++ land. |
762 | 0 | w->stack_base_ = stack_top - (w->stack_size_ - kStackBufferSize); |
763 | |
|
764 | 0 | w->Run(); |
765 | |
|
766 | 0 | Mutex::ScopedLock lock(w->mutex_); |
767 | 0 | w->env()->SetImmediateThreadsafe( |
768 | 0 | [w = std::unique_ptr<Worker>(w)](Environment* env) { |
769 | 0 | if (w->has_ref_) |
770 | 0 | env->add_refs(-1); |
771 | 0 | w->JoinThread(); |
772 | | // implicitly delete w |
773 | 0 | }); |
774 | 0 | }, static_cast<void*>(w)); |
775 | |
|
776 | 0 | if (ret == 0) { |
777 | | // The object now owns the created thread and should not be garbage |
778 | | // collected until that finishes. |
779 | 0 | w->ClearWeak(); |
780 | |
|
781 | 0 | if (w->has_ref_) |
782 | 0 | w->env()->add_refs(1); |
783 | |
|
784 | 0 | w->env()->add_sub_worker_context(w); |
785 | 0 | } else { |
786 | 0 | w->stopped_ = true; |
787 | 0 | w->tid_.reset(); |
788 | |
|
789 | 0 | char err_buf[128]; |
790 | 0 | uv_err_name_r(ret, err_buf, sizeof(err_buf)); |
791 | 0 | { |
792 | 0 | Isolate* isolate = w->env()->isolate(); |
793 | 0 | HandleScope handle_scope(isolate); |
794 | 0 | THROW_ERR_WORKER_INIT_FAILED(isolate, err_buf); |
795 | 0 | } |
796 | 0 | } |
797 | 0 | } |
798 | | |
799 | 0 | void Worker::StopThread(const FunctionCallbackInfo<Value>& args) { |
800 | 0 | Worker* w; |
801 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
802 | | |
803 | 0 | Debug(w, "Worker %llu is getting stopped by parent", w->thread_id_.id); |
804 | 0 | w->Exit(ExitCode::kGenericUserError); |
805 | 0 | } |
806 | | |
807 | 0 | void Worker::Ref(const FunctionCallbackInfo<Value>& args) { |
808 | 0 | Worker* w; |
809 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
810 | 0 | if (!w->has_ref_ && w->tid_.has_value()) { |
811 | 0 | w->has_ref_ = true; |
812 | 0 | w->env()->add_refs(1); |
813 | 0 | } |
814 | 0 | } |
815 | | |
816 | 0 | void Worker::HasRef(const FunctionCallbackInfo<Value>& args) { |
817 | 0 | Worker* w; |
818 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
819 | 0 | args.GetReturnValue().Set(w->has_ref_); |
820 | 0 | } |
821 | | |
822 | 0 | void Worker::Unref(const FunctionCallbackInfo<Value>& args) { |
823 | 0 | Worker* w; |
824 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
825 | 0 | if (w->has_ref_ && w->tid_.has_value()) { |
826 | 0 | w->has_ref_ = false; |
827 | 0 | w->env()->add_refs(-1); |
828 | 0 | } |
829 | 0 | } |
830 | | |
831 | | class WorkerCpuUsageTaker : public AsyncWrap { |
832 | | public: |
833 | | WorkerCpuUsageTaker(Environment* env, Local<Object> obj) |
834 | 0 | : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERCPUUSAGE) {} |
835 | | |
836 | | SET_NO_MEMORY_INFO() |
837 | | SET_MEMORY_INFO_NAME(WorkerCpuUsageTaker) |
838 | | SET_SELF_SIZE(WorkerCpuUsageTaker) |
839 | | }; |
840 | | |
841 | 0 | void Worker::CpuUsage(const FunctionCallbackInfo<Value>& args) { |
842 | 0 | Worker* w; |
843 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
844 | | |
845 | 0 | Environment* env = w->env(); |
846 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); |
847 | 0 | Local<Object> wrap; |
848 | 0 | if (!env->worker_cpu_usage_taker_template() |
849 | 0 | ->NewInstance(env->context()) |
850 | 0 | .ToLocal(&wrap)) { |
851 | 0 | return; |
852 | 0 | } |
853 | | |
854 | 0 | BaseObjectPtr<WorkerCpuUsageTaker> taker = |
855 | 0 | MakeDetachedBaseObject<WorkerCpuUsageTaker>(env, wrap); |
856 | |
|
857 | 0 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), |
858 | 0 | env](Environment* worker_env) mutable { |
859 | 0 | auto cpu_usage_stats = std::make_unique<uv_rusage_t>(); |
860 | 0 | int err = uv_getrusage_thread(cpu_usage_stats.get()); |
861 | |
|
862 | 0 | env->SetImmediateThreadsafe( |
863 | 0 | [taker = std::move(taker), |
864 | 0 | cpu_usage_stats = std::move(cpu_usage_stats), |
865 | 0 | err = err](Environment* env) mutable { |
866 | 0 | Isolate* isolate = env->isolate(); |
867 | 0 | HandleScope handle_scope(isolate); |
868 | 0 | Context::Scope context_scope(env->context()); |
869 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); |
870 | |
|
871 | 0 | Local<Value> argv[] = { |
872 | 0 | Null(isolate), |
873 | 0 | Undefined(isolate), |
874 | 0 | }; |
875 | |
|
876 | 0 | if (err) { |
877 | 0 | argv[0] = UVException( |
878 | 0 | isolate, err, "uv_getrusage_thread", nullptr, nullptr, nullptr); |
879 | 0 | } else { |
880 | 0 | auto tmpl = env->cpu_usage_template(); |
881 | 0 | if (tmpl.IsEmpty()) { |
882 | 0 | static constexpr std::string_view names[] = { |
883 | 0 | "user", |
884 | 0 | "system", |
885 | 0 | }; |
886 | 0 | tmpl = DictionaryTemplate::New(isolate, names); |
887 | 0 | env->set_cpu_usage_template(tmpl); |
888 | 0 | } |
889 | |
|
890 | 0 | MaybeLocal<Value> values[] = { |
891 | 0 | Number::New(isolate, |
892 | 0 | 1e6 * cpu_usage_stats->ru_utime.tv_sec + |
893 | 0 | cpu_usage_stats->ru_utime.tv_usec), |
894 | 0 | Number::New(isolate, |
895 | 0 | 1e6 * cpu_usage_stats->ru_stime.tv_sec + |
896 | 0 | cpu_usage_stats->ru_stime.tv_usec), |
897 | 0 | }; |
898 | 0 | if (!NewDictionaryInstanceNullProto(env->context(), tmpl, values) |
899 | 0 | .ToLocal(&argv[1])) { |
900 | 0 | return; |
901 | 0 | } |
902 | 0 | } |
903 | | |
904 | 0 | taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); |
905 | 0 | }, |
906 | 0 | CallbackFlags::kUnrefed); |
907 | 0 | }); |
908 | |
|
909 | 0 | if (scheduled) { |
910 | 0 | args.GetReturnValue().Set(wrap); |
911 | 0 | } |
912 | 0 | } |
913 | | |
914 | | class WorkerCpuProfileTaker final : public AsyncWrap { |
915 | | public: |
916 | | WorkerCpuProfileTaker(Environment* env, Local<Object> obj) |
917 | 0 | : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERCPUPROFILE) {} |
918 | | |
919 | | SET_NO_MEMORY_INFO() |
920 | | SET_MEMORY_INFO_NAME(WorkerCpuProfileTaker) |
921 | | SET_SELF_SIZE(WorkerCpuProfileTaker) |
922 | | }; |
923 | | |
924 | 0 | void Worker::StartCpuProfile(const FunctionCallbackInfo<Value>& args) { |
925 | 0 | Worker* w; |
926 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
927 | 0 | Environment* env = w->env(); |
928 | |
|
929 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); |
930 | 0 | Local<Object> wrap; |
931 | 0 | if (!env->worker_cpu_profile_taker_template() |
932 | 0 | ->NewInstance(env->context()) |
933 | 0 | .ToLocal(&wrap)) { |
934 | 0 | return; |
935 | 0 | } |
936 | | |
937 | 0 | BaseObjectPtr<WorkerCpuProfileTaker> taker = |
938 | 0 | MakeDetachedBaseObject<WorkerCpuProfileTaker>(env, wrap); |
939 | |
|
940 | 0 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), |
941 | 0 | env](Environment* worker_env) mutable { |
942 | 0 | CpuProfilingResult result = worker_env->StartCpuProfile(); |
943 | 0 | env->SetImmediateThreadsafe( |
944 | 0 | [taker = std::move(taker), result = result](Environment* env) mutable { |
945 | 0 | Isolate* isolate = env->isolate(); |
946 | 0 | HandleScope handle_scope(isolate); |
947 | 0 | Context::Scope context_scope(env->context()); |
948 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); |
949 | 0 | Local<Value> argv[] = { |
950 | 0 | Null(isolate), // error |
951 | 0 | Undefined(isolate), // profile id |
952 | 0 | }; |
953 | 0 | if (result.status == CpuProfilingStatus::kErrorTooManyProfilers) { |
954 | 0 | argv[0] = ERR_CPU_PROFILE_TOO_MANY( |
955 | 0 | isolate, "There are too many CPU profiles"); |
956 | 0 | } else if (result.status == CpuProfilingStatus::kStarted) { |
957 | 0 | argv[1] = Number::New(isolate, result.id); |
958 | 0 | } |
959 | 0 | taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); |
960 | 0 | }, |
961 | 0 | CallbackFlags::kUnrefed); |
962 | 0 | }); |
963 | |
|
964 | 0 | if (scheduled) { |
965 | 0 | args.GetReturnValue().Set(wrap); |
966 | 0 | } |
967 | 0 | } |
968 | | |
969 | 0 | void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) { |
970 | 0 | Worker* w; |
971 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
972 | | |
973 | 0 | Environment* env = w->env(); |
974 | 0 | CHECK(args[0]->IsUint32()); |
975 | 0 | uint32_t profile_id = args[0]->Uint32Value(env->context()).FromJust(); |
976 | |
|
977 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); |
978 | 0 | Local<Object> wrap; |
979 | 0 | if (!env->worker_cpu_profile_taker_template() |
980 | 0 | ->NewInstance(env->context()) |
981 | 0 | .ToLocal(&wrap)) { |
982 | 0 | return; |
983 | 0 | } |
984 | | |
985 | 0 | BaseObjectPtr<WorkerCpuProfileTaker> taker = |
986 | 0 | MakeDetachedBaseObject<WorkerCpuProfileTaker>(env, wrap); |
987 | |
|
988 | 0 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), |
989 | 0 | profile_id = profile_id, |
990 | 0 | env](Environment* worker_env) mutable { |
991 | 0 | bool found = false; |
992 | 0 | auto json_out_stream = std::make_unique<node::JSONOutputStream>(); |
993 | 0 | CpuProfile* profile = worker_env->StopCpuProfile(profile_id); |
994 | 0 | if (profile) { |
995 | 0 | profile->Serialize(json_out_stream.get(), |
996 | 0 | CpuProfile::SerializationFormat::kJSON); |
997 | 0 | profile->Delete(); |
998 | 0 | found = true; |
999 | 0 | } |
1000 | 0 | env->SetImmediateThreadsafe( |
1001 | 0 | [taker = std::move(taker), |
1002 | 0 | json_out_stream = std::move(json_out_stream), |
1003 | 0 | found](Environment* env) mutable { |
1004 | 0 | Isolate* isolate = env->isolate(); |
1005 | 0 | HandleScope handle_scope(isolate); |
1006 | 0 | Context::Scope context_scope(env->context()); |
1007 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); |
1008 | 0 | Local<Value> argv[] = { |
1009 | 0 | Null(isolate), // error |
1010 | 0 | Undefined(isolate), // profile |
1011 | 0 | }; |
1012 | 0 | if (found) { |
1013 | 0 | Local<Value> result; |
1014 | 0 | if (!ToV8Value(env->context(), |
1015 | 0 | json_out_stream->out_stream().str(), |
1016 | 0 | isolate) |
1017 | 0 | .ToLocal(&result)) { |
1018 | 0 | return; |
1019 | 0 | } |
1020 | 0 | argv[1] = result; |
1021 | 0 | } else { |
1022 | 0 | argv[0] = |
1023 | 0 | ERR_CPU_PROFILE_NOT_STARTED(isolate, "CPU profile not started"); |
1024 | 0 | } |
1025 | 0 | taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); |
1026 | 0 | }, |
1027 | 0 | CallbackFlags::kUnrefed); |
1028 | 0 | }); |
1029 | |
|
1030 | 0 | if (scheduled) { |
1031 | 0 | args.GetReturnValue().Set(wrap); |
1032 | 0 | } |
1033 | 0 | } |
1034 | | |
1035 | | class WorkerHeapProfileTaker final : public AsyncWrap { |
1036 | | public: |
1037 | | WorkerHeapProfileTaker(Environment* env, Local<Object> obj) |
1038 | 0 | : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPPROFILE) {} |
1039 | | |
1040 | | SET_NO_MEMORY_INFO() |
1041 | | SET_MEMORY_INFO_NAME(WorkerHeapProfileTaker) |
1042 | | SET_SELF_SIZE(WorkerHeapProfileTaker) |
1043 | | }; |
1044 | | |
1045 | 0 | void Worker::StartHeapProfile(const FunctionCallbackInfo<Value>& args) { |
1046 | 0 | Worker* w; |
1047 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
1048 | 0 | Environment* env = w->env(); |
1049 | |
|
1050 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); |
1051 | 0 | Local<Object> wrap; |
1052 | 0 | if (!env->worker_heap_profile_taker_template() |
1053 | 0 | ->NewInstance(env->context()) |
1054 | 0 | .ToLocal(&wrap)) { |
1055 | 0 | return; |
1056 | 0 | } |
1057 | | |
1058 | 0 | BaseObjectPtr<WorkerHeapProfileTaker> taker = |
1059 | 0 | MakeDetachedBaseObject<WorkerHeapProfileTaker>(env, wrap); |
1060 | |
|
1061 | 0 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), |
1062 | 0 | env](Environment* worker_env) mutable { |
1063 | 0 | v8::HeapProfiler* profiler = worker_env->isolate()->GetHeapProfiler(); |
1064 | 0 | bool success = profiler->StartSamplingHeapProfiler(); |
1065 | 0 | env->SetImmediateThreadsafe( |
1066 | 0 | [taker = std::move(taker), |
1067 | 0 | success = success](Environment* env) mutable { |
1068 | 0 | Isolate* isolate = env->isolate(); |
1069 | 0 | HandleScope handle_scope(isolate); |
1070 | 0 | Context::Scope context_scope(env->context()); |
1071 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); |
1072 | 0 | Local<Value> argv[] = { |
1073 | 0 | Null(isolate), // error |
1074 | 0 | }; |
1075 | 0 | if (!success) { |
1076 | 0 | argv[0] = ERR_HEAP_PROFILE_HAVE_BEEN_STARTED( |
1077 | 0 | isolate, "heap profiler have been started"); |
1078 | 0 | } |
1079 | 0 | taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); |
1080 | 0 | }, |
1081 | 0 | CallbackFlags::kUnrefed); |
1082 | 0 | }); |
1083 | |
|
1084 | 0 | if (scheduled) { |
1085 | 0 | args.GetReturnValue().Set(wrap); |
1086 | 0 | } |
1087 | 0 | } |
1088 | | |
1089 | | static void buildHeapProfileNode(Isolate* isolate, |
1090 | | const AllocationProfile::Node* node, |
1091 | 0 | JSONWriter* writer) { |
1092 | 0 | size_t selfSize = 0; |
1093 | 0 | for (const auto& allocation : node->allocations) |
1094 | 0 | selfSize += allocation.size * allocation.count; |
1095 | |
|
1096 | 0 | writer->json_keyvalue("selfSize", selfSize); |
1097 | 0 | writer->json_keyvalue("id", node->node_id); |
1098 | 0 | writer->json_objectstart("callFrame"); |
1099 | 0 | writer->json_keyvalue("scriptId", node->script_id); |
1100 | 0 | writer->json_keyvalue("lineNumber", node->line_number - 1); |
1101 | 0 | writer->json_keyvalue("columnNumber", node->column_number - 1); |
1102 | 0 | node::Utf8Value name(isolate, node->name); |
1103 | 0 | node::Utf8Value script_name(isolate, node->script_name); |
1104 | 0 | writer->json_keyvalue("functionName", *name); |
1105 | 0 | writer->json_keyvalue("url", *script_name); |
1106 | 0 | writer->json_objectend(); |
1107 | |
|
1108 | 0 | writer->json_arraystart("children"); |
1109 | 0 | for (const auto* child : node->children) { |
1110 | 0 | writer->json_start(); |
1111 | 0 | buildHeapProfileNode(isolate, child, writer); |
1112 | 0 | writer->json_end(); |
1113 | 0 | } |
1114 | 0 | writer->json_arrayend(); |
1115 | 0 | } |
1116 | | |
1117 | 0 | static bool serializeProfile(Isolate* isolate, std::ostringstream& out_stream) { |
1118 | 0 | HandleScope scope(isolate); |
1119 | 0 | HeapProfiler* profiler = isolate->GetHeapProfiler(); |
1120 | 0 | std::unique_ptr<AllocationProfile> profile(profiler->GetAllocationProfile()); |
1121 | 0 | if (!profile) { |
1122 | 0 | return false; |
1123 | 0 | } |
1124 | 0 | JSONWriter writer(out_stream, false); |
1125 | 0 | writer.json_start(); |
1126 | |
|
1127 | 0 | writer.json_arraystart("samples"); |
1128 | 0 | for (const auto& sample : profile->GetSamples()) { |
1129 | 0 | writer.json_start(); |
1130 | 0 | writer.json_keyvalue("size", sample.size * sample.count); |
1131 | 0 | writer.json_keyvalue("nodeId", sample.node_id); |
1132 | 0 | writer.json_keyvalue("ordinal", static_cast<double>(sample.sample_id)); |
1133 | 0 | writer.json_end(); |
1134 | 0 | } |
1135 | 0 | writer.json_arrayend(); |
1136 | |
|
1137 | 0 | writer.json_objectstart("head"); |
1138 | 0 | buildHeapProfileNode(isolate, profile->GetRootNode(), &writer); |
1139 | 0 | writer.json_objectend(); |
1140 | |
|
1141 | 0 | writer.json_end(); |
1142 | 0 | profiler->StopSamplingHeapProfiler(); |
1143 | 0 | return true; |
1144 | 0 | } |
1145 | | |
1146 | 0 | void Worker::StopHeapProfile(const FunctionCallbackInfo<Value>& args) { |
1147 | 0 | Worker* w; |
1148 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
1149 | | |
1150 | 0 | Environment* env = w->env(); |
1151 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); |
1152 | 0 | Local<Object> wrap; |
1153 | 0 | if (!env->worker_heap_profile_taker_template() |
1154 | 0 | ->NewInstance(env->context()) |
1155 | 0 | .ToLocal(&wrap)) { |
1156 | 0 | return; |
1157 | 0 | } |
1158 | | |
1159 | 0 | BaseObjectPtr<WorkerHeapProfileTaker> taker = |
1160 | 0 | MakeDetachedBaseObject<WorkerHeapProfileTaker>(env, wrap); |
1161 | |
|
1162 | 0 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), |
1163 | 0 | env](Environment* worker_env) mutable { |
1164 | 0 | std::ostringstream out_stream; |
1165 | 0 | bool success = serializeProfile(worker_env->isolate(), out_stream); |
1166 | 0 | env->SetImmediateThreadsafe( |
1167 | 0 | [taker = std::move(taker), |
1168 | 0 | out_stream = std::move(out_stream), |
1169 | 0 | success = success](Environment* env) mutable { |
1170 | 0 | Isolate* isolate = env->isolate(); |
1171 | 0 | HandleScope handle_scope(isolate); |
1172 | 0 | Context::Scope context_scope(env->context()); |
1173 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); |
1174 | 0 | Local<Value> argv[] = { |
1175 | 0 | Null(isolate), // error |
1176 | 0 | Undefined(isolate), // profile |
1177 | 0 | }; |
1178 | 0 | if (success) { |
1179 | 0 | Local<Value> result; |
1180 | 0 | if (!ToV8Value(env->context(), out_stream.str(), isolate) |
1181 | 0 | .ToLocal(&result)) { |
1182 | 0 | return; |
1183 | 0 | } |
1184 | 0 | argv[1] = result; |
1185 | 0 | } else { |
1186 | 0 | argv[0] = ERR_HEAP_PROFILE_NOT_STARTED(isolate, |
1187 | 0 | "heap profile not started"); |
1188 | 0 | } |
1189 | 0 | taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); |
1190 | 0 | }, |
1191 | 0 | CallbackFlags::kUnrefed); |
1192 | 0 | }); |
1193 | |
|
1194 | 0 | if (scheduled) { |
1195 | 0 | args.GetReturnValue().Set(wrap); |
1196 | 0 | } |
1197 | 0 | } |
1198 | | class WorkerHeapStatisticsTaker : public AsyncWrap { |
1199 | | public: |
1200 | | WorkerHeapStatisticsTaker(Environment* env, Local<Object> obj) |
1201 | 0 | : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPSTATISTICS) {} |
1202 | | |
1203 | | SET_NO_MEMORY_INFO() |
1204 | | SET_MEMORY_INFO_NAME(WorkerHeapStatisticsTaker) |
1205 | | SET_SELF_SIZE(WorkerHeapStatisticsTaker) |
1206 | | }; |
1207 | | |
1208 | 0 | void Worker::GetHeapStatistics(const FunctionCallbackInfo<Value>& args) { |
1209 | 0 | Worker* w; |
1210 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
1211 | | |
1212 | 0 | Environment* env = w->env(); |
1213 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); |
1214 | 0 | Local<Object> wrap; |
1215 | 0 | if (!env->worker_heap_statistics_taker_template() |
1216 | 0 | ->NewInstance(env->context()) |
1217 | 0 | .ToLocal(&wrap)) { |
1218 | 0 | return; |
1219 | 0 | } |
1220 | | |
1221 | | // The created WorkerHeapStatisticsTaker is an object owned by main |
1222 | | // thread's Isolate, it can not be accessed by worker thread |
1223 | 0 | std::unique_ptr<BaseObjectPtr<WorkerHeapStatisticsTaker>> taker = |
1224 | 0 | std::make_unique<BaseObjectPtr<WorkerHeapStatisticsTaker>>( |
1225 | 0 | MakeDetachedBaseObject<WorkerHeapStatisticsTaker>(env, wrap)); |
1226 | | |
1227 | | // Interrupt the worker thread and take a snapshot, then schedule a call |
1228 | | // on the parent thread that turns that snapshot into a readable stream. |
1229 | 0 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), |
1230 | 0 | env](Environment* worker_env) mutable { |
1231 | | // We create a unique pointer to HeapStatistics so that the actual object |
1232 | | // it's not copied in the lambda, but only the pointer is. |
1233 | 0 | auto heap_stats = std::make_unique<HeapStatistics>(); |
1234 | 0 | worker_env->isolate()->GetHeapStatistics(heap_stats.get()); |
1235 | | |
1236 | | // Here, the worker thread temporarily owns the WorkerHeapStatisticsTaker |
1237 | | // object. |
1238 | |
|
1239 | 0 | env->SetImmediateThreadsafe( |
1240 | 0 | [taker = std::move(taker), |
1241 | 0 | heap_stats = std::move(heap_stats)](Environment* env) mutable { |
1242 | 0 | Isolate* isolate = env->isolate(); |
1243 | 0 | HandleScope handle_scope(isolate); |
1244 | 0 | Context::Scope context_scope(env->context()); |
1245 | |
|
1246 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker->get()); |
1247 | |
|
1248 | 0 | auto tmpl = env->heap_statistics_template(); |
1249 | 0 | if (tmpl.IsEmpty()) { |
1250 | 0 | std::string_view heap_stats_names[] = { |
1251 | 0 | "total_heap_size", |
1252 | 0 | "total_heap_size_executable", |
1253 | 0 | "total_physical_size", |
1254 | 0 | "total_available_size", |
1255 | 0 | "used_heap_size", |
1256 | 0 | "heap_size_limit", |
1257 | 0 | "malloced_memory", |
1258 | 0 | "peak_malloced_memory", |
1259 | 0 | "does_zap_garbage", |
1260 | 0 | "number_of_native_contexts", |
1261 | 0 | "number_of_detached_contexts", |
1262 | 0 | "total_global_handles_size", |
1263 | 0 | "used_global_handles_size", |
1264 | 0 | "external_memory", |
1265 | 0 | "total_allocated_bytes", |
1266 | 0 | }; |
1267 | 0 | tmpl = DictionaryTemplate::New(isolate, heap_stats_names); |
1268 | 0 | env->set_heap_statistics_template(tmpl); |
1269 | 0 | } |
1270 | | |
1271 | | // Define an array of property values |
1272 | 0 | MaybeLocal<Value> heap_stats_values[] = { |
1273 | 0 | Number::New(isolate, heap_stats->total_heap_size()), |
1274 | 0 | Number::New(isolate, heap_stats->total_heap_size_executable()), |
1275 | 0 | Number::New(isolate, heap_stats->total_physical_size()), |
1276 | 0 | Number::New(isolate, heap_stats->total_available_size()), |
1277 | 0 | Number::New(isolate, heap_stats->used_heap_size()), |
1278 | 0 | Number::New(isolate, heap_stats->heap_size_limit()), |
1279 | 0 | Number::New(isolate, heap_stats->malloced_memory()), |
1280 | 0 | Number::New(isolate, heap_stats->peak_malloced_memory()), |
1281 | 0 | Boolean::New(isolate, heap_stats->does_zap_garbage()), |
1282 | 0 | Number::New(isolate, heap_stats->number_of_native_contexts()), |
1283 | 0 | Number::New(isolate, heap_stats->number_of_detached_contexts()), |
1284 | 0 | Number::New(isolate, heap_stats->total_global_handles_size()), |
1285 | 0 | Number::New(isolate, heap_stats->used_global_handles_size()), |
1286 | 0 | Number::New(isolate, heap_stats->external_memory()), |
1287 | 0 | Number::New(isolate, heap_stats->total_allocated_bytes())}; |
1288 | |
|
1289 | 0 | Local<Object> obj; |
1290 | 0 | if (!NewDictionaryInstanceNullProto( |
1291 | 0 | env->context(), tmpl, heap_stats_values) |
1292 | 0 | .ToLocal(&obj)) { |
1293 | 0 | return; |
1294 | 0 | } |
1295 | 0 | Local<Value> args[] = {obj}; |
1296 | 0 | taker->get()->MakeCallback( |
1297 | 0 | env->ondone_string(), arraysize(args), args); |
1298 | | // implicitly delete `taker` |
1299 | 0 | }, |
1300 | 0 | CallbackFlags::kUnrefed); |
1301 | | |
1302 | | // Now, the lambda is delivered to the main thread, as a result, the |
1303 | | // WorkerHeapStatisticsTaker object is delivered to the main thread, too. |
1304 | 0 | }); |
1305 | |
|
1306 | 0 | if (scheduled) { |
1307 | 0 | args.GetReturnValue().Set(wrap); |
1308 | 0 | } else { |
1309 | 0 | args.GetReturnValue().Set(Local<Object>()); |
1310 | 0 | } |
1311 | 0 | } |
1312 | | |
1313 | 0 | void Worker::GetResourceLimits(const FunctionCallbackInfo<Value>& args) { |
1314 | 0 | Worker* w; |
1315 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
1316 | 0 | args.GetReturnValue().Set(w->GetResourceLimits(args.GetIsolate())); |
1317 | 0 | } |
1318 | | |
1319 | 0 | Local<Float64Array> Worker::GetResourceLimits(Isolate* isolate) const { |
1320 | 0 | Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, sizeof(resource_limits_)); |
1321 | |
|
1322 | 0 | memcpy(ab->Data(), resource_limits_, sizeof(resource_limits_)); |
1323 | 0 | return Float64Array::New(ab, 0, kTotalResourceLimitCount); |
1324 | 0 | } |
1325 | | |
1326 | | void Worker::Exit(ExitCode code, |
1327 | | const char* error_code, |
1328 | 0 | const char* error_message) { |
1329 | 0 | Mutex::ScopedLock lock(mutex_); |
1330 | 0 | Debug(this, |
1331 | 0 | "Worker %llu called Exit(%d, %s, %s)", |
1332 | 0 | thread_id_.id, |
1333 | 0 | static_cast<int>(code), |
1334 | 0 | error_code, |
1335 | 0 | error_message); |
1336 | |
|
1337 | 0 | if (error_code != nullptr) { |
1338 | 0 | custom_error_ = error_code; |
1339 | 0 | custom_error_str_ = error_message; |
1340 | 0 | } |
1341 | |
|
1342 | 0 | if (env_ != nullptr) { |
1343 | 0 | exit_code_ = code; |
1344 | 0 | Stop(env_); |
1345 | 0 | } else { |
1346 | 0 | stopped_ = true; |
1347 | 0 | } |
1348 | 0 | } |
1349 | | |
1350 | 0 | bool Worker::IsNotIndicativeOfMemoryLeakAtExit() const { |
1351 | | // Worker objects always stay alive as long as the child thread, regardless |
1352 | | // of whether they are being referenced in the parent thread. |
1353 | 0 | return true; |
1354 | 0 | } |
1355 | | |
1356 | | class WorkerHeapSnapshotTaker : public AsyncWrap { |
1357 | | public: |
1358 | | WorkerHeapSnapshotTaker(Environment* env, Local<Object> obj) |
1359 | 0 | : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPSNAPSHOT) {} |
1360 | | |
1361 | | SET_NO_MEMORY_INFO() |
1362 | | SET_MEMORY_INFO_NAME(WorkerHeapSnapshotTaker) |
1363 | | SET_SELF_SIZE(WorkerHeapSnapshotTaker) |
1364 | | }; |
1365 | | |
1366 | 0 | void Worker::TakeHeapSnapshot(const FunctionCallbackInfo<Value>& args) { |
1367 | 0 | Worker* w; |
1368 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
1369 | 0 | CHECK_EQ(args.Length(), 1); |
1370 | 0 | auto options = heap::GetHeapSnapshotOptions(args[0]); |
1371 | |
|
1372 | 0 | Debug(w, "Worker %llu taking heap snapshot", w->thread_id_.id); |
1373 | |
|
1374 | 0 | Environment* env = w->env(); |
1375 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); |
1376 | 0 | Local<Object> wrap; |
1377 | 0 | if (!env->worker_heap_snapshot_taker_template() |
1378 | 0 | ->NewInstance(env->context()).ToLocal(&wrap)) { |
1379 | 0 | return; |
1380 | 0 | } |
1381 | | |
1382 | | // The created WorkerHeapSnapshotTaker is an object owned by main |
1383 | | // thread's Isolate, it can not be accessed by worker thread |
1384 | 0 | std::unique_ptr<BaseObjectPtr<WorkerHeapSnapshotTaker>> taker = |
1385 | 0 | std::make_unique<BaseObjectPtr<WorkerHeapSnapshotTaker>>( |
1386 | 0 | MakeDetachedBaseObject<WorkerHeapSnapshotTaker>(env, wrap)); |
1387 | | |
1388 | | // Interrupt the worker thread and take a snapshot, then schedule a call |
1389 | | // on the parent thread that turns that snapshot into a readable stream. |
1390 | 0 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), env, options]( |
1391 | 0 | Environment* worker_env) mutable { |
1392 | 0 | heap::HeapSnapshotPointer snapshot{ |
1393 | 0 | worker_env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)}; |
1394 | 0 | CHECK(snapshot); |
1395 | | |
1396 | | // Here, the worker thread temporarily owns the WorkerHeapSnapshotTaker |
1397 | | // object. |
1398 | | |
1399 | 0 | env->SetImmediateThreadsafe( |
1400 | 0 | [taker = std::move(taker), |
1401 | 0 | snapshot = std::move(snapshot)](Environment* env) mutable { |
1402 | 0 | HandleScope handle_scope(env->isolate()); |
1403 | 0 | Context::Scope context_scope(env->context()); |
1404 | |
|
1405 | 0 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker->get()); |
1406 | 0 | BaseObjectPtr<AsyncWrap> stream = |
1407 | 0 | heap::CreateHeapSnapshotStream(env, std::move(snapshot)); |
1408 | 0 | Local<Value> args[] = {stream->object()}; |
1409 | 0 | taker->get()->MakeCallback( |
1410 | 0 | env->ondone_string(), arraysize(args), args); |
1411 | | // implicitly delete `taker` |
1412 | 0 | }, |
1413 | 0 | CallbackFlags::kUnrefed); |
1414 | | |
1415 | | // Now, the lambda is delivered to the main thread, as a result, the |
1416 | | // WorkerHeapSnapshotTaker object is delivered to the main thread, too. |
1417 | 0 | }); |
1418 | |
|
1419 | 0 | if (scheduled) { |
1420 | 0 | args.GetReturnValue().Set(wrap); |
1421 | 0 | } else { |
1422 | 0 | args.GetReturnValue().Set(Local<Object>()); |
1423 | 0 | } |
1424 | 0 | } |
1425 | | |
1426 | 0 | void Worker::LoopIdleTime(const FunctionCallbackInfo<Value>& args) { |
1427 | 0 | Worker* w; |
1428 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
1429 | | |
1430 | 0 | Mutex::ScopedLock lock(w->mutex_); |
1431 | | // Using w->is_stopped() here leads to a deadlock, and checking is_stopped() |
1432 | | // before locking the mutex is a race condition. So manually do the same |
1433 | | // check. |
1434 | 0 | if (w->stopped_ || w->env_ == nullptr) |
1435 | 0 | return args.GetReturnValue().Set(-1); |
1436 | | |
1437 | 0 | uint64_t idle_time = uv_metrics_idle_time(w->env_->event_loop()); |
1438 | 0 | args.GetReturnValue().Set(1.0 * idle_time / 1e6); |
1439 | 0 | } |
1440 | | |
1441 | 0 | void Worker::LoopStartTime(const FunctionCallbackInfo<Value>& args) { |
1442 | 0 | Worker* w; |
1443 | 0 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); |
1444 | | |
1445 | 0 | Mutex::ScopedLock lock(w->mutex_); |
1446 | | // Using w->is_stopped() here leads to a deadlock, and checking is_stopped() |
1447 | | // before locking the mutex is a race condition. So manually do the same |
1448 | | // check. |
1449 | 0 | if (w->stopped_ || w->env_ == nullptr) |
1450 | 0 | return args.GetReturnValue().Set(-1); |
1451 | | |
1452 | 0 | double loop_start_time = w->env_->performance_state()->milestones[ |
1453 | 0 | node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START]; |
1454 | 0 | CHECK_GE(loop_start_time, 0); |
1455 | 0 | args.GetReturnValue().Set(loop_start_time / 1e6); |
1456 | 0 | } |
1457 | | |
1458 | | namespace { |
1459 | | |
1460 | | // Return the MessagePort that is global for this Environment and communicates |
1461 | | // with the internal [kPort] port of the JS Worker class in the parent thread. |
1462 | 0 | void GetEnvMessagePort(const FunctionCallbackInfo<Value>& args) { |
1463 | 0 | Environment* env = Environment::GetCurrent(args); |
1464 | 0 | Local<Object> port = env->message_port(); |
1465 | 0 | CHECK_IMPLIES(!env->is_main_thread(), !port.IsEmpty()); |
1466 | 0 | if (!port.IsEmpty()) { |
1467 | 0 | args.GetReturnValue().Set(port); |
1468 | 0 | } |
1469 | 0 | } |
1470 | | |
1471 | | void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, |
1472 | 35 | Local<ObjectTemplate> target) { |
1473 | 35 | Isolate* isolate = isolate_data->isolate(); |
1474 | | |
1475 | 35 | { |
1476 | 35 | Local<FunctionTemplate> w = NewFunctionTemplate(isolate, Worker::New); |
1477 | | |
1478 | 35 | w->InstanceTemplate()->SetInternalFieldCount( |
1479 | 35 | Worker::kInternalFieldCount); |
1480 | 35 | w->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); |
1481 | | |
1482 | 35 | SetProtoMethod(isolate, w, "startThread", Worker::StartThread); |
1483 | 35 | SetProtoMethod(isolate, w, "stopThread", Worker::StopThread); |
1484 | 35 | SetProtoMethod(isolate, w, "hasRef", Worker::HasRef); |
1485 | 35 | SetProtoMethod(isolate, w, "ref", Worker::Ref); |
1486 | 35 | SetProtoMethod(isolate, w, "unref", Worker::Unref); |
1487 | 35 | SetProtoMethod(isolate, w, "getResourceLimits", Worker::GetResourceLimits); |
1488 | 35 | SetProtoMethod(isolate, w, "takeHeapSnapshot", Worker::TakeHeapSnapshot); |
1489 | 35 | SetProtoMethod(isolate, w, "loopIdleTime", Worker::LoopIdleTime); |
1490 | 35 | SetProtoMethod(isolate, w, "loopStartTime", Worker::LoopStartTime); |
1491 | 35 | SetProtoMethod(isolate, w, "getHeapStatistics", Worker::GetHeapStatistics); |
1492 | 35 | SetProtoMethod(isolate, w, "cpuUsage", Worker::CpuUsage); |
1493 | 35 | SetProtoMethod(isolate, w, "startCpuProfile", Worker::StartCpuProfile); |
1494 | 35 | SetProtoMethod(isolate, w, "stopCpuProfile", Worker::StopCpuProfile); |
1495 | 35 | SetProtoMethod(isolate, w, "startHeapProfile", Worker::StartHeapProfile); |
1496 | 35 | SetProtoMethod(isolate, w, "stopHeapProfile", Worker::StopHeapProfile); |
1497 | | |
1498 | 35 | SetConstructorFunction(isolate, target, "Worker", w); |
1499 | 35 | } |
1500 | | |
1501 | 35 | { |
1502 | 35 | Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); |
1503 | | |
1504 | 35 | wst->InstanceTemplate()->SetInternalFieldCount( |
1505 | 35 | WorkerHeapSnapshotTaker::kInternalFieldCount); |
1506 | 35 | wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); |
1507 | | |
1508 | 35 | Local<String> wst_string = |
1509 | 35 | FIXED_ONE_BYTE_STRING(isolate, "WorkerHeapSnapshotTaker"); |
1510 | 35 | wst->SetClassName(wst_string); |
1511 | 35 | isolate_data->set_worker_heap_snapshot_taker_template( |
1512 | 35 | wst->InstanceTemplate()); |
1513 | 35 | } |
1514 | | |
1515 | 35 | { |
1516 | 35 | Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); |
1517 | | |
1518 | 35 | wst->InstanceTemplate()->SetInternalFieldCount( |
1519 | 35 | WorkerHeapSnapshotTaker::kInternalFieldCount); |
1520 | 35 | wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); |
1521 | | |
1522 | 35 | Local<String> wst_string = |
1523 | 35 | FIXED_ONE_BYTE_STRING(isolate, "WorkerHeapStatisticsTaker"); |
1524 | 35 | wst->SetClassName(wst_string); |
1525 | 35 | isolate_data->set_worker_heap_statistics_taker_template( |
1526 | 35 | wst->InstanceTemplate()); |
1527 | 35 | } |
1528 | | |
1529 | 35 | { |
1530 | 35 | Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); |
1531 | | |
1532 | 35 | wst->InstanceTemplate()->SetInternalFieldCount( |
1533 | 35 | WorkerCpuUsageTaker::kInternalFieldCount); |
1534 | 35 | wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); |
1535 | | |
1536 | 35 | Local<String> wst_string = |
1537 | 35 | FIXED_ONE_BYTE_STRING(isolate, "WorkerCpuUsageTaker"); |
1538 | 35 | wst->SetClassName(wst_string); |
1539 | 35 | isolate_data->set_worker_cpu_usage_taker_template(wst->InstanceTemplate()); |
1540 | 35 | } |
1541 | | |
1542 | 35 | { |
1543 | 35 | Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); |
1544 | | |
1545 | 35 | wst->InstanceTemplate()->SetInternalFieldCount( |
1546 | 35 | WorkerCpuProfileTaker::kInternalFieldCount); |
1547 | 35 | wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); |
1548 | | |
1549 | 35 | Local<String> wst_string = |
1550 | 35 | FIXED_ONE_BYTE_STRING(isolate, "WorkerCpuProfileTaker"); |
1551 | 35 | wst->SetClassName(wst_string); |
1552 | 35 | isolate_data->set_worker_cpu_profile_taker_template( |
1553 | 35 | wst->InstanceTemplate()); |
1554 | 35 | } |
1555 | | |
1556 | 35 | { |
1557 | 35 | Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); |
1558 | | |
1559 | 35 | wst->InstanceTemplate()->SetInternalFieldCount( |
1560 | 35 | WorkerHeapProfileTaker::kInternalFieldCount); |
1561 | 35 | wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); |
1562 | | |
1563 | 35 | Local<String> wst_string = |
1564 | 35 | FIXED_ONE_BYTE_STRING(isolate, "WorkerHeapProfileTaker"); |
1565 | 35 | wst->SetClassName(wst_string); |
1566 | 35 | isolate_data->set_worker_heap_profile_taker_template( |
1567 | 35 | wst->InstanceTemplate()); |
1568 | 35 | } |
1569 | | |
1570 | 35 | SetMethod(isolate, target, "getEnvMessagePort", GetEnvMessagePort); |
1571 | 35 | } |
1572 | | |
1573 | | void CreateWorkerPerContextProperties(Local<Object> target, |
1574 | | Local<Value> unused, |
1575 | | Local<Context> context, |
1576 | 35 | void* priv) { |
1577 | 35 | Environment* env = Environment::GetCurrent(context); |
1578 | 35 | Isolate* isolate = env->isolate(); |
1579 | | |
1580 | 35 | target |
1581 | 35 | ->Set(env->context(), |
1582 | 35 | env->thread_id_string(), |
1583 | 35 | Number::New(isolate, static_cast<double>(env->thread_id()))) |
1584 | 35 | .Check(); |
1585 | | |
1586 | 35 | target |
1587 | 35 | ->Set(env->context(), |
1588 | 35 | env->thread_name_string(), |
1589 | 35 | String::NewFromUtf8(isolate, |
1590 | 35 | env->thread_name().data(), |
1591 | 35 | NewStringType::kNormal, |
1592 | 35 | env->thread_name().size()) |
1593 | 35 | .ToLocalChecked()) |
1594 | 35 | .Check(); |
1595 | | |
1596 | 35 | target |
1597 | 35 | ->Set(env->context(), |
1598 | 35 | FIXED_ONE_BYTE_STRING(isolate, "isMainThread"), |
1599 | 35 | Boolean::New(isolate, env->is_main_thread())) |
1600 | 35 | .Check(); |
1601 | | |
1602 | 35 | Worker* worker = env->isolate_data()->worker_context(); |
1603 | 35 | bool is_internal = worker != nullptr && worker->is_internal(); |
1604 | | |
1605 | | // Set the is_internal property |
1606 | 35 | target |
1607 | 35 | ->Set(env->context(), |
1608 | 35 | FIXED_ONE_BYTE_STRING(isolate, "isInternalThread"), |
1609 | 35 | Boolean::New(isolate, is_internal)) |
1610 | 35 | .Check(); |
1611 | | |
1612 | 35 | target |
1613 | 35 | ->Set(env->context(), |
1614 | 35 | FIXED_ONE_BYTE_STRING(isolate, "ownsProcessState"), |
1615 | 35 | Boolean::New(isolate, env->owns_process_state())) |
1616 | 35 | .Check(); |
1617 | | |
1618 | 35 | if (!env->is_main_thread()) { |
1619 | 0 | target |
1620 | 0 | ->Set(env->context(), |
1621 | 0 | FIXED_ONE_BYTE_STRING(isolate, "resourceLimits"), |
1622 | 0 | env->worker_context()->GetResourceLimits(isolate)) |
1623 | 0 | .Check(); |
1624 | 0 | } |
1625 | | |
1626 | 35 | NODE_DEFINE_CONSTANT(target, kMaxYoungGenerationSizeMb); |
1627 | 35 | NODE_DEFINE_CONSTANT(target, kMaxOldGenerationSizeMb); |
1628 | 35 | NODE_DEFINE_CONSTANT(target, kCodeRangeSizeMb); |
1629 | 35 | NODE_DEFINE_CONSTANT(target, kStackSizeMb); |
1630 | 35 | NODE_DEFINE_CONSTANT(target, kTotalResourceLimitCount); |
1631 | 35 | } |
1632 | | |
1633 | 0 | void RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
1634 | 0 | registry->Register(GetEnvMessagePort); |
1635 | 0 | registry->Register(Worker::New); |
1636 | 0 | registry->Register(Worker::StartThread); |
1637 | 0 | registry->Register(Worker::StopThread); |
1638 | 0 | registry->Register(Worker::HasRef); |
1639 | 0 | registry->Register(Worker::Ref); |
1640 | 0 | registry->Register(Worker::Unref); |
1641 | 0 | registry->Register(Worker::GetResourceLimits); |
1642 | 0 | registry->Register(Worker::TakeHeapSnapshot); |
1643 | 0 | registry->Register(Worker::LoopIdleTime); |
1644 | 0 | registry->Register(Worker::LoopStartTime); |
1645 | 0 | registry->Register(Worker::GetHeapStatistics); |
1646 | 0 | registry->Register(Worker::CpuUsage); |
1647 | 0 | registry->Register(Worker::StartCpuProfile); |
1648 | 0 | registry->Register(Worker::StopCpuProfile); |
1649 | 0 | registry->Register(Worker::StartHeapProfile); |
1650 | 0 | registry->Register(Worker::StopHeapProfile); |
1651 | 0 | } |
1652 | | |
1653 | | } // anonymous namespace |
1654 | | } // namespace worker |
1655 | | } // namespace node |
1656 | | |
1657 | | NODE_BINDING_CONTEXT_AWARE_INTERNAL( |
1658 | | worker, node::worker::CreateWorkerPerContextProperties) |
1659 | | NODE_BINDING_PER_ISOLATE_INIT(worker, |
1660 | | node::worker::CreateWorkerPerIsolateProperties) |
1661 | | NODE_BINDING_EXTERNAL_REFERENCE(worker, |
1662 | | node::worker::RegisterExternalReferences) |