diff options
Diffstat (limited to 'test/known_issues/test-cluster-disconnect-handles.js')
-rw-r--r-- | test/known_issues/test-cluster-disconnect-handles.js | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/test/known_issues/test-cluster-disconnect-handles.js b/test/known_issues/test-cluster-disconnect-handles.js new file mode 100644 index 0000000000..4b481f0a7b --- /dev/null +++ b/test/known_issues/test-cluster-disconnect-handles.js @@ -0,0 +1,99 @@ +/* eslint-disable no-debugger */ +// Flags: --expose_internals +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const cluster = require('cluster'); +const net = require('net'); + +const Protocol = require('_debugger').Protocol; + +if (common.isWindows) { + common.skip('SCHED_RR not reliable on Windows'); + return; +} + +cluster.schedulingPolicy = cluster.SCHED_RR; + +// Worker sends back a "I'm here" message, then immediately suspends +// inside the debugger. The master connects to the debug agent first, +// connects to the TCP server second, then disconnects the worker and +// unsuspends it again. The ultimate goal of this tortured exercise +// is to make sure the connection is still sitting in the master's +// pending handle queue. +if (cluster.isMaster) { + let isKilling = false; + const handles = require('internal/cluster/utils').handles; + const address = common.hasIPv6 ? '[::1]' : common.localhostIPv4; + cluster.setupMaster({ execArgv: [`--debug=${address}:${common.PORT}`] }); + const worker = cluster.fork(); + worker.once('exit', common.mustCall((code, signal) => { + assert.strictEqual(code, 0, 'worker did not exit normally'); + assert.strictEqual(signal, null, 'worker did not exit normally'); + })); + worker.on('message', common.mustCall((message) => { + assert.strictEqual(Array.isArray(message), true); + assert.strictEqual(message[0], 'listening'); + let continueRecv = false; + const address = message[1]; + const host = address.address; + const debugClient = net.connect({ host, port: common.PORT }); + const protocol = new Protocol(); + debugClient.setEncoding('utf8'); + debugClient.on('data', (data) => protocol.execute(data)); + debugClient.once('connect', common.mustCall(() => { + protocol.onResponse = common.mustCall((res) => { + protocol.onResponse = (res) => { + // It can happen that the first continue was sent before the break + // event was received. If that's the case, send also a continue from + // here so the worker exits + if (res.body.command === 'continue') { + continueRecv = true; + } else if (res.body.event === 'break' && continueRecv) { + const req = protocol.serialize({ command: 'continue' }); + debugClient.write(req); + } + }; + const conn = net.connect({ host, port: address.port }); + conn.once('connect', common.mustCall(() => { + conn.destroy(); + assert.notDeepStrictEqual(handles, {}); + worker.disconnect(); + assert.deepStrictEqual(handles, {}); + // Always send the continue, as the break event might have already + // been received. + const req = protocol.serialize({ command: 'continue' }); + debugClient.write(req); + })); + }); + })); + })); + process.on('exit', () => assert.deepStrictEqual(handles, {})); + process.on('uncaughtException', function(ex) { + // Make sure we clean up so as not to leave a stray worker process running + // if we encounter a connection or other error + if (!worker.isDead()) { + if (!isKilling) { + isKilling = true; + worker.once('exit', function() { + throw ex; + }); + worker.process.kill(); + } + return; + } + throw ex; + }); +} else { + const server = net.createServer((socket) => socket.pipe(socket)); + const cb = () => { + process.send(['listening', server.address()]); + debugger; + }; + if (common.hasIPv6) + server.listen(0, '::1', cb); + else + server.listen(0, common.localhostIPv4, cb); + process.on('disconnect', process.exit); +} |