diff --git a/lib/internal/cluster/child.js b/lib/internal/cluster/child.js index 48e81d8744310b..5cee07b3534cf6 100644 --- a/lib/internal/cluster/child.js +++ b/lib/internal/cluster/child.js @@ -16,6 +16,7 @@ const { owner_symbol } = require('internal/async_hooks').symbols; const Worker = require('internal/cluster/worker'); const { internal, sendHelper } = require('internal/cluster/utils'); const { exitCodes: { kNoFailure } } = internalBinding('errors'); +const { TIMEOUT_MAX } = require('internal/timers'); const cluster = new EventEmitter(); const handles = new SafeMap(); @@ -162,6 +163,21 @@ function rr(message, { indexesKey, index }, cb) { let key = message.key; + let fakeHandle = null; + + function ref() { + if (!fakeHandle) { + fakeHandle = setInterval(noop, TIMEOUT_MAX); + } + } + + function unref() { + if (fakeHandle) { + clearInterval(fakeHandle); + fakeHandle = null; + } + } + function listen(backlog) { // TODO(bnoordhuis) Send a message to the primary that tells it to // update the backlog size. The actual backlog should probably be @@ -177,7 +193,7 @@ function rr(message, { indexesKey, index }, cb) { // the primary. if (key === undefined) return; - + unref(); send({ act: 'close', key }); handles.delete(key); removeIndexesKey(indexesKey, index); @@ -191,12 +207,10 @@ function rr(message, { indexesKey, index }, cb) { return 0; } - // Faux handle. Mimics a TCPWrap with just enough fidelity to get away - // with it. Fools net.Server into thinking that it's backed by a real - // handle. Use a noop function for ref() and unref() because the control - // channel is going to keep the worker alive anyway. - const handle = { close, listen, ref: noop, unref: noop }; - + // Faux handle. net.Server is not associated with handle, + // so we control its state(ref or unref) by setInterval. + const handle = { close, listen, ref, unref }; + handle.ref(); if (message.sockname) { handle.getsockname = getsockname; // TCP handles only. } diff --git a/test/parallel/test-cluster-rr-handle-keep-loop-alive.js b/test/parallel/test-cluster-rr-handle-keep-loop-alive.js new file mode 100644 index 00000000000000..0b18408a192ba1 --- /dev/null +++ b/test/parallel/test-cluster-rr-handle-keep-loop-alive.js @@ -0,0 +1,23 @@ +'use strict'; + +const common = require('../common'); +const cluster = require('cluster'); +const net = require('net'); +const assert = require('assert'); + +cluster.schedulingPolicy = cluster.SCHED_RR; + +if (cluster.isPrimary) { + let exited = false; + const worker = cluster.fork(); + worker.on('exit', () => { + exited = true; + }); + setTimeout(() => { + assert.ok(!exited); + worker.kill(); + }, 3000); +} else { + const server = net.createServer(common.mustNotCall()); + server.listen(0, common.mustCall(() => process.channel.unref())); +} diff --git a/test/parallel/test-cluster-rr-handle-ref-unref.js b/test/parallel/test-cluster-rr-handle-ref-unref.js new file mode 100644 index 00000000000000..403bbefd4dd69b --- /dev/null +++ b/test/parallel/test-cluster-rr-handle-ref-unref.js @@ -0,0 +1,20 @@ +'use strict'; + +const common = require('../common'); +const cluster = require('cluster'); +const net = require('net'); + +cluster.schedulingPolicy = cluster.SCHED_RR; + +if (cluster.isPrimary) { + const worker = cluster.fork(); + worker.on('exit', common.mustCall()); +} else { + const server = net.createServer(common.mustNotCall()); + server.listen(0, common.mustCall(() => { + server.ref(); + server.unref(); + process.channel.unref(); + })); + server.unref(); +}