Skip to content

Commit 1c3a2eb

Browse files
eugeneotargos
authored andcommitted
inspector: enable Inspector JS API in workers
PR-URL: #22769 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 600c225 commit 1c3a2eb

21 files changed

+99
-35
lines changed

lib/inspector.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const util = require('util');
1414
const { Connection, open, url } = process.binding('inspector');
1515
const { originalConsole } = require('internal/process/per_thread');
1616

17-
if (!Connection || !require('internal/worker').isMainThread)
17+
if (!Connection)
1818
throw new ERR_INSPECTOR_NOT_AVAILABLE();
1919

2020
const connectionSymbol = Symbol('connectionProperty');

lib/internal/util/inspector.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
'use strict';
22

3-
// TODO(addaleax): Figure out how to integrate the inspector with workers.
4-
const hasInspector = process.config.variables.v8_enable_inspector === 1 &&
5-
require('internal/worker').isMainThread;
3+
const hasInspector = process.config.variables.v8_enable_inspector === 1;
64
const inspector = hasInspector ? require('inspector') : undefined;
75

86
let session;

lib/internal/worker.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const { MessagePort, MessageChannel } = internalBinding('messaging');
1717
const { handle_onclose } = internalBinding('symbols');
1818
const { clearAsyncIdStack } = require('internal/async_hooks');
1919
const { serializeError, deserializeError } = require('internal/error-serdes');
20+
const { pathToFileURL } = require('url');
2021

2122
util.inherits(MessagePort, EventEmitter);
2223

@@ -257,8 +258,9 @@ class Worker extends EventEmitter {
257258
}
258259
}
259260

261+
const url = options.eval ? null : pathToFileURL(filename);
260262
// Set up the C++ handle for the worker, as well as some internal wiring.
261-
this[kHandle] = new WorkerImpl();
263+
this[kHandle] = new WorkerImpl(url);
262264
this[kHandle].onexit = (code) => this[kOnExit](code);
263265
this[kPort] = this[kHandle].messagePort;
264266
this[kPort].on('message', (data) => this[kOnMessage](data));

src/inspector/main_thread_interface.cc

-7
Original file line numberDiff line numberDiff line change
@@ -354,13 +354,6 @@ void MainThreadHandle::Reset() {
354354
main_thread_ = nullptr;
355355
}
356356

357-
Agent* MainThreadHandle::GetInspectorAgent() {
358-
Mutex::ScopedLock scoped_lock(block_lock_);
359-
if (main_thread_ == nullptr)
360-
return nullptr;
361-
return main_thread_->inspector_agent();
362-
}
363-
364357
std::unique_ptr<InspectorSessionDelegate>
365358
MainThreadHandle::MakeDelegateThreadSafe(
366359
std::unique_ptr<InspectorSessionDelegate> delegate) {

src/inspector/main_thread_interface.h

-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ class MainThreadHandle : public std::enable_shared_from_this<MainThreadHandle> {
5454
return ++next_object_id_;
5555
}
5656
bool Post(std::unique_ptr<Request> request);
57-
Agent* GetInspectorAgent();
5857
std::unique_ptr<InspectorSessionDelegate> MakeDelegateThreadSafe(
5958
std::unique_ptr<InspectorSessionDelegate> delegate);
6059
bool Expired();

src/inspector/tracing_agent.cc

+8-5
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,14 @@ DispatchResponse TracingAgent::start(
7474
if (categories_set.empty())
7575
return DispatchResponse::Error("At least one category should be enabled");
7676

77-
trace_writer_ = env_->tracing_agent_writer()->agent()->AddClient(
78-
categories_set,
79-
std::unique_ptr<InspectorTraceWriter>(
80-
new InspectorTraceWriter(frontend_.get())),
81-
tracing::Agent::kIgnoreDefaultCategories);
77+
auto* writer = env_->tracing_agent_writer();
78+
if (writer != nullptr) {
79+
trace_writer_ = env_->tracing_agent_writer()->agent()->AddClient(
80+
categories_set,
81+
std::unique_ptr<InspectorTraceWriter>(
82+
new InspectorTraceWriter(frontend_.get())),
83+
tracing::Agent::kIgnoreDefaultCategories);
84+
}
8285
return DispatchResponse::OK();
8386
}
8487

src/inspector_agent.cc

+25-5
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ static int StartDebugSignalHandler() {
189189

190190
const int CONTEXT_GROUP_ID = 1;
191191

192+
std::string GetWorkerLabel(node::Environment* env) {
193+
std::ostringstream result;
194+
result << "Worker[" << env->thread_id() << "]";
195+
return result.str();
196+
}
197+
192198
class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
193199
public protocol::FrontendChannel {
194200
public:
@@ -373,10 +379,13 @@ void NotifyClusterWorkersDebugEnabled(Environment* env) {
373379

374380
class NodeInspectorClient : public V8InspectorClient {
375381
public:
376-
explicit NodeInspectorClient(node::Environment* env) : env_(env) {
382+
explicit NodeInspectorClient(node::Environment* env, bool is_main)
383+
: env_(env), is_main_(is_main) {
377384
client_ = V8Inspector::create(env->isolate(), this);
378385
// TODO(bnoordhuis) Make name configurable from src/node.cc.
379-
ContextInfo info(GetHumanReadableProcessName());
386+
std::string name =
387+
is_main_ ? GetHumanReadableProcessName() : GetWorkerLabel(env);
388+
ContextInfo info(name);
380389
info.is_default = true;
381390
contextCreated(env->context(), info);
382391
}
@@ -593,6 +602,7 @@ class NodeInspectorClient : public V8InspectorClient {
593602
}
594603

595604
node::Environment* env_;
605+
bool is_main_;
596606
bool running_nested_loop_ = false;
597607
std::unique_ptr<V8Inspector> client_;
598608
std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
@@ -610,13 +620,23 @@ Agent::Agent(Environment* env)
610620
: parent_env_(env),
611621
debug_options_(env->options()->debug_options) {}
612622

613-
Agent::~Agent() = default;
623+
Agent::~Agent() {
624+
if (start_io_thread_async.data == this) {
625+
start_io_thread_async.data = nullptr;
626+
// This is global, will never get freed
627+
uv_close(reinterpret_cast<uv_handle_t*>(&start_io_thread_async), nullptr);
628+
}
629+
}
614630

615631
bool Agent::Start(const std::string& path,
616-
std::shared_ptr<DebugOptions> options) {
632+
std::shared_ptr<DebugOptions> options,
633+
bool is_main) {
634+
if (options == nullptr) {
635+
options = std::make_shared<DebugOptions>();
636+
}
617637
path_ = path;
618638
debug_options_ = options;
619-
client_ = std::make_shared<NodeInspectorClient>(parent_env_);
639+
client_ = std::make_shared<NodeInspectorClient>(parent_env_, is_main);
620640
if (parent_env_->is_main_thread()) {
621641
CHECK_EQ(0, uv_async_init(parent_env_->event_loop(),
622642
&start_io_thread_async,

src/inspector_agent.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ class Agent {
4747
~Agent();
4848

4949
// Create client_, may create io_ if option enabled
50-
bool Start(const std::string& path, std::shared_ptr<DebugOptions> options);
50+
bool Start(const std::string& path,
51+
std::shared_ptr<DebugOptions> options,
52+
bool is_worker);
5153
// Stop and destroy io_
5254
void Stop();
5355

src/node.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ static struct {
327327
// right away on the websocket port and fails to bind/etc, this will return
328328
// false.
329329
return env->inspector_agent()->Start(
330-
script_path == nullptr ? "" : script_path, options);
330+
script_path == nullptr ? "" : script_path, options, true);
331331
}
332332

333333
bool InspectorStarted(Environment* env) {

src/node_worker.cc

+29-3
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,26 @@ namespace {
3535
uint64_t next_thread_id = 1;
3636
Mutex next_thread_id_mutex;
3737

38+
#if NODE_USE_V8_PLATFORM && HAVE_INSPECTOR
39+
void StartWorkerInspector(Environment* child, const std::string& url) {
40+
child->inspector_agent()->Start(url, nullptr, false);
41+
}
42+
43+
void WaitForWorkerInspectorToStop(Environment* child) {
44+
child->inspector_agent()->WaitForDisconnect();
45+
child->inspector_agent()->Stop();
46+
}
47+
48+
#else
49+
// No-ops
50+
void StartWorkerInspector(Environment* child, const std::string& url) {}
51+
void WaitForWorkerInspectorToStop(Environment* child) {}
52+
#endif
53+
3854
} // anonymous namespace
3955

40-
Worker::Worker(Environment* env, Local<Object> wrap)
41-
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER) {
56+
Worker::Worker(Environment* env, Local<Object> wrap, const std::string& url)
57+
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER), url_(url) {
4258
// Generate a new thread id.
4359
{
4460
Mutex::ScopedLock next_thread_id_lock(next_thread_id_mutex);
@@ -155,6 +171,7 @@ void Worker::Run() {
155171
env_->async_hooks()->pop_async_id(1);
156172

157173
Debug(this, "Loaded environment for worker %llu", thread_id_);
174+
StartWorkerInspector(env_.get(), url_);
158175
}
159176

160177
{
@@ -215,6 +232,7 @@ void Worker::Run() {
215232
env_->stop_sub_worker_contexts();
216233
env_->RunCleanup();
217234
RunAtExit(env_.get());
235+
WaitForWorkerInspectorToStop(env_.get());
218236

219237
{
220238
Mutex::ScopedLock stopped_lock(stopped_mutex_);
@@ -350,7 +368,15 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
350368
return;
351369
}
352370

353-
new Worker(env, args.This());
371+
std::string url;
372+
// Argument might be a string or URL
373+
if (args.Length() == 1 && !args[0]->IsNullOrUndefined()) {
374+
Utf8Value value(
375+
args.GetIsolate(),
376+
args[0]->ToString(env->context()).FromMaybe(v8::Local<v8::String>()));
377+
url.append(value.out(), value.length());
378+
}
379+
new Worker(env, args.This(), url);
354380
}
355381

356382
void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {

src/node_worker.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace worker {
1212
// A worker thread, as represented in its parent thread.
1313
class Worker : public AsyncWrap {
1414
public:
15-
Worker(Environment* env, v8::Local<v8::Object> wrap);
15+
Worker(Environment* env, v8::Local<v8::Object> wrap, const std::string& url);
1616
~Worker();
1717

1818
// Run the worker. This is only called from the worker thread.
@@ -52,6 +52,7 @@ class Worker : public AsyncWrap {
5252
uv_loop_t loop_;
5353
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data_;
5454
DeleteFnPtr<Environment, FreeEnvironment> env_;
55+
const std::string url_;
5556
v8::Isolate* isolate_ = nullptr;
5657
DeleteFnPtr<ArrayBufferAllocator, FreeArrayBufferAllocator>
5758
array_buffer_allocator_;

test/common/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,11 @@ was disabled at compile time.
351351
Skip the rest of the tests in the current file when the Node.js executable
352352
was compiled with a pointer size smaller than 64 bits.
353353

354+
### skipIfWorker()
355+
356+
Skip the rest of the tests in the current file when not running on a main
357+
thread.
358+
354359
## ArrayStream Module
355360

356361
The `ArrayStream` module provides a simple `Stream` that pushes elements from

test/common/index.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -612,10 +612,6 @@ function skipIfInspectorDisabled() {
612612
if (process.config.variables.v8_enable_inspector === 0) {
613613
skip('V8 inspector is disabled');
614614
}
615-
if (!isMainThread) {
616-
// TODO(addaleax): Fix me.
617-
skip('V8 inspector is not available in Workers');
618-
}
619615
}
620616

621617
function skipIf32Bits() {
@@ -624,6 +620,12 @@ function skipIf32Bits() {
624620
}
625621
}
626622

623+
function skipIfWorker() {
624+
if (!isMainThread) {
625+
skip('This test only works on a main thread');
626+
}
627+
}
628+
627629
function getArrayBufferViews(buf) {
628630
const { buffer, byteOffset, byteLength } = buf;
629631

@@ -740,6 +742,7 @@ module.exports = {
740742
skipIf32Bits,
741743
skipIfEslintMissing,
742744
skipIfInspectorDisabled,
745+
skipIfWorker,
743746

744747
get localhostIPv6() { return '::1'; },
745748

test/parallel/test-bootstrap-modules.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ const list = process.moduleLoadList.slice();
1111

1212
const assert = require('assert');
1313

14-
assert(list.length <= 74, list);
14+
assert(list.length <= 75, list);

test/parallel/test-inspector-port-zero-cluster.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const common = require('../common');
44

55
common.skipIfInspectorDisabled();
6+
common.skipIfWorker();
67

78
// Assert that even when started with `--inspect=0` workers are assigned
89
// consecutive (i.e. deterministically predictable) debug ports

test/parallel/test-inspector-tracing-domain.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const common = require('../common');
44

55
common.skipIfInspectorDisabled();
6+
common.skipIfWorker(); // https://github.com/nodejs/node/issues/22767
67

78
const assert = require('assert');
89
const { Session } = require('inspector');

test/parallel/test-trace-events-dynamic-enable.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const common = require('../common');
44

55
common.skipIfInspectorDisabled();
6+
common.skipIfWorker(); // https://github.com/nodejs/node/issues/22767
67

78
const assert = require('assert');
89
const { performance } = require('perf_hooks');

test/parallel/test-warn-sigprof.js

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ common.skipIfInspectorDisabled();
1010
if (common.isWindows)
1111
common.skip('test does not apply to Windows');
1212

13+
common.skipIfWorker(); // Worker inspector never has a server running
14+
1315
common.expectWarning('Warning',
1416
'process.on(SIGPROF) is reserved while debugging',
1517
common.noWarnCode);

test/sequential/test-inspector-async-hook-setup-at-signal.js

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ common.skipIf32Bits();
66
const { NodeInstance } = require('../common/inspector-helper.js');
77
const assert = require('assert');
88

9+
common.skipIfWorker(); // Signal starts a server for a main thread inspector
10+
911
const script = `
1012
process._rawDebug('Waiting until a signal enables the inspector...');
1113
let waiting = setInterval(waitUntilDebugged, 50);

test/sequential/test-inspector-contexts.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ async function testContextCreatedAndDestroyed() {
3333
// the PID.
3434
strictEqual(name.includes(`[${process.pid}]`), true);
3535
} else {
36-
strictEqual(`${process.argv0}[${process.pid}]`, name);
36+
let expects = `${process.argv0}[${process.pid}]`;
37+
if (!common.isMainThread) {
38+
expects = `Worker[${require('worker_threads').threadId}]`;
39+
}
40+
strictEqual(expects, name);
3741
}
3842
strictEqual(origin, '',
3943
JSON.stringify(contextCreated));

test/sequential/test-inspector-port-cluster.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const common = require('../common');
44

55
common.skipIfInspectorDisabled();
6+
common.skipIfWorker();
67

78
const assert = require('assert');
89
const cluster = require('cluster');

0 commit comments

Comments
 (0)