Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cluster: fixing debug port logic for forking workers #9659

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 78 additions & 12 deletions lib/internal/cluster/master.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ const cluster = new EventEmitter();
const intercom = new EventEmitter();
const SCHED_NONE = 1;
const SCHED_RR = 2;
let debugPortOffset = 0;

const debugArgRegex = /^(--inspect(?:-brk|-port)?|--debug-port)(?:=(.*))?$/;

const hasDebugArg = process.execArgv.some((argv) => debugArgRegex.test(argv));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as line 82


if (hasDebugArg) {
// Increase debugPortOffset only if master was started with debug.
debugPortOffset = 1;
}

module.exports = cluster;

Expand All @@ -24,7 +34,6 @@ cluster.SCHED_NONE = SCHED_NONE; // Leave it to the operating system.
cluster.SCHED_RR = SCHED_RR; // Master distributes connections.

var ids = 0;
var debugPortOffset = 1;
var initialized = false;

// XXX(bnoordhuis) Fold cluster.schedulingPolicy into cluster.settings?
Expand Down Expand Up @@ -70,6 +79,15 @@ cluster.setupMaster = function(options) {
assert(schedulingPolicy === SCHED_NONE || schedulingPolicy === SCHED_RR,
`Bad cluster.schedulingPolicy: ${schedulingPolicy}`);

const hasDebugArg = process.execArgv.some((argv) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a new undocumented property process._inspectorEnbale that tells you that 😃

Copy link
Contributor Author

@mutantcornholio mutantcornholio Jun 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What the hell "Enbale" stands for? :'D
I thought you've made a typo.. but no!
2017-06-05 1 21 31

I'll probably make myself a baseball cap with "Inspector Enbale" on it

Copy link
Contributor

@refack refack Jun 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It stands for a typo-that-slips-in-when-you-use-a-bad-IDE-and-have-to-finish-a-feature-two-days-before-the-release-deadline
Mea culpa 🤦‍♂️
Ref: #13460

Copy link

@myshov myshov Jun 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@refack It's more funny if you know that enbale phonetically is similar to swearing in Russian, which used by someone in situations when he got upset :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Hebrew it's a common name for girls (also the swinging thing inside bells 🔔 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welp, this property has been deleted ¯\_(ツ)_/¯

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO wait for #13228 to land, and use inspector.url()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither process._inspectorEnbaled or inspector.url() work for this purpose, because they don't catch --inspect-port. That option doesn't "enable" the inspector (turn it on), it just changes the default port in case it is turned on later. So, the regex is necessary, AFAICT.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but that's an (IMHO uninteresting) edge case. I think this PR stands firm without covering the node --inspector-port=XXXX script.js case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@refack the current code does support this use-case, but if the regex is removed as you suggest, and replaced with inspector.url(), then it will stop supporting the use-case.

return debugArgRegex.test(argv);
});

if (hasDebugArg) {
// Increase debugPortOffset only if master was started with debug
debugPortOffset = 1;
}

process.nextTick(setupSettingsNT, settings);

process.on('internalMessage', (message) => {
Expand All @@ -96,25 +114,73 @@ function setupSettingsNT(settings) {
}

function createWorkerProcess(id, env) {
var workerEnv = util._extend({}, process.env);
var execArgv = cluster.settings.execArgv.slice();
var debugPort = 0;
// If master process started with debug, workers should use ports with offset
// This code parses execArgv from cluster.settings
// and augments values of ports in following flags:
// --inspect, --inspect-brk, --inspect-port, --debug-port.

const workerEnv = util._extend({}, process.env);
const execArgv = cluster.settings.execArgv.slice();
const ipv6Regex = /^(\[[\d:]+\])(?::(.+))?$/;

util._extend(workerEnv, env);
workerEnv.NODE_UNIQUE_ID = '' + id;

for (var i = 0; i < execArgv.length; i++) {
const match = execArgv[i].match(
/^(--inspect|--inspect-(brk|port)|--debug|--debug-(brk|port))(=\d+)?$/
);
// Cycle is reversed on purpose:
// node currently selects debug port from last argument
// so we're changing last argument and breaking the cycle.
let i = execArgv.length;
while (i--) {
if (debugArgRegex.test(execArgv[i])) {
let debugPort = 0;
let debugHost = '';

const splitDebugArg = execArgv[i].split('=');

let resultArg = splitDebugArg[0] + '=';
const debugSocket = splitDebugArg[1];

if (debugSocket !== undefined) {
const ipv6Match = debugSocket.match(ipv6Regex);

if (ipv6Match) {
debugHost = ipv6Match[1];

if (ipv6Match[2]) {
debugPort = +ipv6Match[2];
}
} else {
const splitDebugSocket = debugSocket.split(':');

if (splitDebugSocket[1]) {
debugHost = splitDebugSocket[0];
debugPort = +splitDebugSocket[1];
} else {
if (/^\d+$/.test(debugSocket)) {
debugPort = +debugSocket;
} else {
debugHost = debugSocket;
}
}
}
}

if (match) {
if (debugPort === 0) {
debugPort = process.debugPort + debugPortOffset;
++debugPortOffset;
debugPort = process.debugPort;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fails after 399cb25
Ref: #13499

}

execArgv[i] = match[1] + '=' + debugPort;
debugPort += debugPortOffset;
++debugPortOffset;

if (debugHost) {
resultArg += debugHost + ':';
}

resultArg += debugPort;

execArgv[i] = resultArg;

break;
}
}

Expand Down
41 changes: 0 additions & 41 deletions test/parallel/test-cluster-inspector-debug-port.js

This file was deleted.

56 changes: 56 additions & 0 deletions test/sequential/test-cluster-inspect-port-manual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';

/**
* This test suite checks debug port numbers
* when node started without --inspect argument
* and debug initiated with cluster.settings.execArgv or similar
*/

const common = require('../common');
const assert = require('assert');
const cluster = require('cluster');

if (cluster.isMaster) {
let message = 'If master is not debugging, ' +
'forked worker should not have --inspect argument';

cluster.fork({message}).on('exit', common.mustCall(checkExitCode));

message = 'When debugging port is set at cluster.settings, ' +
'forked worker should have debugPort without offset';
cluster.settings.execArgv = ['--inspect=' + common.PORT];
cluster.fork({
portSet: common.PORT,
message
}).on('exit', common.mustCall(checkExitCode));

message = 'When using custom port number, ' +
'forked worker should have debugPort incremented from that port';
cluster.fork({
portSet: common.PORT + 1,
message
}).on('exit', common.mustCall(checkExitCode));

cluster.fork({
portSet: common.PORT + 2,
message
}).on('exit', common.mustCall(checkExitCode));
} else {
const hasDebugArg = process.execArgv.some(function(arg) {
return /--inspect/.test(arg);
});

assert.strictEqual(hasDebugArg, process.env.portSet !== undefined);
hasDebugArg && assert.strictEqual(
process.debugPort,
+process.env.portSet,
process.env.message +
`; expected port: ${+process.env.portSet}, actual: ${process.debugPort}`
);
process.exit();
}

function checkExitCode(code, signal) {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
}
141 changes: 141 additions & 0 deletions test/sequential/test-cluster-inspector-debug-port.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';
// Flags: --inspect={PORT}
const common = require('../common');

common.skipIfInspectorDisabled();

const assert = require('assert');
const cluster = require('cluster');
const debuggerPort = common.PORT;

let offset = 0;

function checkExitCode(code, signal) {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
}

if (cluster.isMaster) {
assert.strictEqual(process.debugPort, debuggerPort);

let message = 'If master is debugging with default port, ' +
'forked worker should have debugPort, with offset = 1';

cluster.fork({
portSet: process.debugPort + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = 'Debug port offset should increment';
cluster.fork({
portSet: process.debugPort + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = 'If master is debugging on non-default port, ' +
'worker ports should be incremented from it';
cluster.setupMaster({
execArgv: ['--inspect=' + common.PORT]
});
cluster.fork({
portSet: common.PORT + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = '--inspect=hostname:port argument format ' +
'should be parsed correctly';
cluster.setupMaster({
execArgv: ['--inspect=localhost:' + (common.PORT + 10)]
});
cluster.fork({
portSet: common.PORT + 10 + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = '--inspect=ipv4addr:port argument format ' +
'should be parsed correctly';
cluster.setupMaster({
execArgv: ['--inspect=' + common.localhostIPv4 + ':' + (common.PORT + 20)]
});
cluster.fork({
portSet: common.PORT + 20 + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = '--inspect=[ipv6addr]:port argument format ' +
'should be parsed correctly';
if (common.hasIPv6) {
cluster.setupMaster({
execArgv: ['--inspect=[::1]:' + (common.PORT + 30)]
});
cluster.fork({
portSet: common.PORT + 30 + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));
}

message = '--inspect=hostname:port argument format ' +
'should be parsed correctly';
cluster.setupMaster({
execArgv: ['--inspect=localhost'],
});
cluster.fork({
portSet: process.debugPort + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = '--inspect=ipv4addr argument format ' +
'should be parsed correctly';
cluster.setupMaster({
execArgv: ['--inspect=' + common.localhostIPv4]
});
cluster.fork({
portSet: process.debugPort + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = '--inspect=[ipv6addr] argument format ' +
'should be parsed correctly';
if (common.hasIPv6) {
cluster.setupMaster({
execArgv: ['--inspect=[::1]']
});
cluster.fork({
portSet: process.debugPort + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));
}

message = '--inspect --debug-port={PORT} argument format ' +
'should be parsed correctly';
cluster.setupMaster({
execArgv: ['--inspect', '--debug-port=' + debuggerPort]
});
cluster.fork({
portSet: process.debugPort + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

message = '--inspect --inspect-port={PORT} argument format ' +
'should be parsed correctly';
cluster.setupMaster({
execArgv: ['--inspect', '--inspect-port=' + debuggerPort]
});
cluster.fork({
portSet: process.debugPort + (++offset),
message
}).on('exit', common.mustCall(checkExitCode));

} else {
const hasDebugArg = process.execArgv.some(function(arg) {
return /inspect/.test(arg);
});

assert.strictEqual(hasDebugArg, true);
assert.strictEqual(
process.debugPort,
+process.env.portSet,
process.env.message
);
process.exit();
}