I have a Node server that creates a child process with fork()
using IPC. At some point the child sends results back to the parent at about 10Hz as part of a long-running task. When the payload passed to process.send()
is small all works well: every message I send is received ~immediately and processed by the parent.
However, when the payload is 'large'—I haven't determined the exact size limit—instead of being immediately received by the parent all payloads are first sent, and only once the child is done its long-running task does the parent receive and process the messages.
tl;dr visual:
Good (happens with small payload):
child: send()
parent: receive()
child: send()
parent: receive()
child: send()
parent: receive()
...
Bad (happens with big payload):
child: send()
child: send()
child: send()
(repeat many times over many seconds)
...
parent: receive()
parent: receive()
parent: receive()
parent: receive()
...
- Is this a bug? (Edit: behavior only occurs on OS X, not Windows or Linux)
- Is there any way to avoid this, other than trying to keep my IPC payload small?
Edit 2: the sample code below uses both time and iteration counter to select when to send an update. (In my actual code it's also possible to send an update after n iterations, or after the loop achieves certain results.) As such a rewrite of the code to use setInterval
/setTimeout
instead of a loop is a last resort for me, as it requires me to remove features.
Edit: Here is test code that reproduces the problem. However, it only reproduces on OS X, not on Windows or Linux:
server.js
const opts = {stdio:['inherit', 'inherit', 'inherit', 'ipc']};
const child = require('child_process').fork('worker.js', [], opts);
child.on('message', msg => console.log(`parent: receive() ${msg.data.length} bytes`, Date.now()));
require('http').createServer((req, res) => {
console.log(req.url);
const match = /\d+/.exec(req.url);
if (match) {
child.send(match[0]*1);
res.writeHead(200, {'Content-Type':'text/plain'});
res.end(`Sending packets of size ${match[0]}`);
} else {
res.writeHead(404, {'Content-Type':'text/plain'});
res.end('what?');
}
}).listen(8080);
worker.js
if (process.send) process.on('message', msg => run(msg));
function run(messageSize) {
const msg = new Array(messageSize+1).join('x');
let lastUpdate = Date.now();
for (let i=0; i<1e7; ++i) {
const now = Date.now();
if ((now-lastUpdate)>200 || i%5000==0) {
console.log(`worker: send() > ${messageSize} bytes`, now);
process.send({action:'update', data:msg});
lastUpdate = Date.now();
}
Math.sqrt(Math.random());
}
console.log('worker done');
}
About around 8k the problem happens. For example, when querying http://localhost:8080/15
vs http://localhost:8080/123456
/15
worker: send() > 15 bytes 1571324249029
parent: receive() 15 bytes 1571324249034
worker: send() > 15 bytes 1571324249235
parent: receive() 15 bytes 1571324249235
worker: send() > 15 bytes 1571324249436
parent: receive() 15 bytes 1571324249436
worker done
/123456
worker: send() > 123456 bytes 1571324276973
worker: send() > 123456 bytes 1571324277174
worker: send() > 123456 bytes 1571324277375
child done
parent: receive() 123456 bytes 1571324277391
parent: receive() 123456 bytes 1571324277391
parent: receive() 123456 bytes 1571324277393
Experienced on both Node v12.7 and v12.12.