diff --git a/doc/api/cluster.md b/doc/api/cluster.md index e2fa8227ad5794..208393674674fd 100644 --- a/doc/api/cluster.md +++ b/doc/api/cluster.md @@ -746,6 +746,9 @@ changes: `'ipc'` entry. When this option is provided, it overrides `silent`. * `uid` {number} Sets the user identity of the process. (See setuid(2).) * `gid` {number} Sets the group identity of the process. (See setgid(2).) + * `inspectPort` {number|function} Sets inspector port of worker. + Accepts number, or function that evaluates to number. By default + each worker gets port, incremented from master's `process.debugPort`. After calling `.setupMaster()` (or `.fork()`) this settings object will contain the settings, including the default values. diff --git a/lib/internal/cluster/master.js b/lib/internal/cluster/master.js index 2d1e2d3097f75b..05663e792b3ba6 100644 --- a/lib/internal/cluster/master.js +++ b/lib/internal/cluster/master.js @@ -12,6 +12,7 @@ const cluster = new EventEmitter(); const intercom = new EventEmitter(); const SCHED_NONE = 1; const SCHED_RR = 2; +const {isLegalPort} = require('internal/net'); module.exports = cluster; @@ -104,8 +105,23 @@ function createWorkerProcess(id, env) { workerEnv.NODE_UNIQUE_ID = '' + id; if (execArgv.some((arg) => arg.match(debugArgRegex))) { - execArgv.push(`--inspect-port=${process.debugPort + debugPortOffset}`); - debugPortOffset++; + let inspectPort; + if ('inspectPort' in cluster.settings) { + if (typeof cluster.settings.inspectPort === 'function') + inspectPort = cluster.settings.inspectPort(); + else + inspectPort = cluster.settings.inspectPort; + + if (!isLegalPort(inspectPort)) { + throw new TypeError('cluster.settings.inspectPort' + + ' is invalid'); + } + } else { + inspectPort = process.debugPort + debugPortOffset; + debugPortOffset++; + } + + execArgv.push(`--inspect-port=${inspectPort}`); } return fork(cluster.settings.exec, cluster.settings.args, { diff --git a/test/inspector/test-inspector-port-cluster.js b/test/inspector/test-inspector-port-cluster.js index f73c0fd3673969..166f5a8d108664 100644 --- a/test/inspector/test-inspector-port-cluster.js +++ b/test/inspector/test-inspector-port-cluster.js @@ -18,7 +18,7 @@ let offset = 0; */ function testRunnerMain() { - spawnMaster({ + let defaultPortCase = spawnMaster({ execArgv: ['--inspect'], workers: [{expectedPort: 9230}] }); @@ -77,42 +77,251 @@ function testRunnerMain() { workers: [{expectedPort: port + 1, expectedHost: '::1'}] }); } -} + // These tests check that setting inspectPort in cluster.settings + // would take effect and override port incrementing behavior + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: port + 2}, + workers: [{expectedPort: port + 2}] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 'addTwo'}, + workers: [ + {expectedPort: port + 2}, + {expectedPort: port + 4} + ] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 'string'}, + workers: [{}] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 'null'}, + workers: [{}] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 'bignumber'}, + workers: [{}] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 'negativenumber'}, + workers: [{}] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 'bignumberfunc'}, + workers: [{}] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 'strfunc'}, + workers: [{}] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [], + clusterSettings: {inspectPort: port, execArgv: ['--inspect']}, + workers: [ + {expectedPort: port} + ] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [`--inspect=${port}`], + clusterSettings: {inspectPort: 0}, + workers: [ + {expectedInitialPort: 0}, + {expectedInitialPort: 0}, + {expectedInitialPort: 0} + ] + }); + + port = debuggerPort + offset++ * 5; + + spawnMaster({ + execArgv: [], + clusterSettings: {inspectPort: 0}, + workers: [ + {expectedInitialPort: 0}, + {expectedInitialPort: 0}, + {expectedInitialPort: 0} + ] + }); + + defaultPortCase.then(() => { + port = debuggerPort + offset++ * 5; + defaultPortCase = spawnMaster({ + execArgv: ['--inspect'], + clusterSettings: {inspectPort: port + 2}, + workers: [ + {expectedInitialPort: port + 2} + ] + }); + }); +} function masterProcessMain() { const workers = JSON.parse(process.env.workers); + const clusterSettings = JSON.parse(process.env.clusterSettings); + let debugPort = process.debugPort; for (const worker of workers) { - cluster.fork({ - expectedPort: worker.expectedPort, - expectedHost: worker.expectedHost - }).on('exit', common.mustCall(checkExitCode)); + const params = {}; + + if (worker.expectedPort) { + params.expectedPort = worker.expectedPort; + } + + if (worker.expectedInitialPort) { + params.expectedInitialPort = worker.expectedInitialPort; + } + + if (worker.expectedHost) { + params.expectedHost = worker.expectedHost; + } + + if (clusterSettings) { + if (clusterSettings.inspectPort === 'addTwo') { + clusterSettings.inspectPort = common.mustCall( + () => { return debugPort += 2; }, + workers.length + ); + } else if (clusterSettings.inspectPort === 'string') { + clusterSettings.inspectPort = 'string'; + cluster.setupMaster(clusterSettings); + + assert.throws(() => { + cluster.fork(params).on('exit', common.mustCall(checkExitCode)); + }, TypeError); + + return; + } else if (clusterSettings.inspectPort === 'null') { + clusterSettings.inspectPort = null; + cluster.setupMaster(clusterSettings); + + assert.throws(() => { + cluster.fork(params).on('exit', common.mustCall(checkExitCode)); + }, TypeError); + + return; + } else if (clusterSettings.inspectPort === 'bignumber') { + clusterSettings.inspectPort = 1293812; + cluster.setupMaster(clusterSettings); + + assert.throws(() => { + cluster.fork(params).on('exit', common.mustCall(checkExitCode)); + }, TypeError); + + return; + } else if (clusterSettings.inspectPort === 'negativenumber') { + clusterSettings.inspectPort = -9776; + cluster.setupMaster(clusterSettings); + + assert.throws(() => { + cluster.fork(params).on('exit', common.mustCall(checkExitCode)); + }, TypeError); + + return; + } else if (clusterSettings.inspectPort === 'bignumberfunc') { + clusterSettings.inspectPort = common.mustCall( + () => 123121, + workers.length + ); + + cluster.setupMaster(clusterSettings); + + assert.throws(() => { + cluster.fork(params).on('exit', common.mustCall(checkExitCode)); + }, TypeError); + + return; + } else if (clusterSettings.inspectPort === 'strfunc') { + clusterSettings.inspectPort = common.mustCall( + () => 'invalidPort', + workers.length + ); + + cluster.setupMaster(clusterSettings); + + assert.throws(() => { + cluster.fork(params).on('exit', common.mustCall(checkExitCode)); + }, TypeError); + + return; + } + cluster.setupMaster(clusterSettings); + } + + cluster.fork(params).on('exit', common.mustCall(checkExitCode)); } } function workerProcessMain() { - const {expectedPort, expectedHost} = process.env; + const {expectedPort, expectedInitialPort, expectedHost} = process.env; + const debugOptions = process.binding('config').debugOptions; + + if ('expectedPort' in process.env) { + assert.strictEqual(process.debugPort, +expectedPort); + } - assert.strictEqual(process.debugPort, +expectedPort); + if ('expectedInitialPort' in process.env) { + assert.strictEqual(debugOptions.port, +expectedInitialPort); + } - if (expectedHost !== 'undefined') { - assert.strictEqual( - process.binding('config').debugOptions.host, - expectedHost - ); + if ('expectedHost' in process.env) { + assert.strictEqual(debugOptions.host, expectedHost); } process.exit(); } -function spawnMaster({execArgv, workers}) { - childProcess.fork(__filename, { - env: { - workers: JSON.stringify(workers), - testProcess: true - }, - execArgv - }).on('exit', common.mustCall(checkExitCode)); +function spawnMaster({execArgv, workers, clusterSettings = {}}) { + return new Promise((resolve) => { + childProcess.fork(__filename, { + env: { + workers: JSON.stringify(workers), + clusterSettings: JSON.stringify(clusterSettings), + testProcess: true + }, + execArgv + }).on('exit', common.mustCall((code, signal) => { + checkExitCode(code, signal); + resolve(); + })); + }); } function checkExitCode(code, signal) {