Repository navigation
IPC channel stops delivering messages to cluster workers #9706
Copy link
Copy link
Closed
Labels
child_processIssues and PRs related to the child_process subsystem.Issues and PRs related to the child_process subsystem.clusterIssues and PRs related to the cluster subsystem.Issues and PRs related to the cluster subsystem.freebsdIssues and PRs related to the FreeBSD platform.Issues and PRs related to the FreeBSD platform.macosIssues and PRs related to the macOS platform.Issues and PRs related to the macOS platform.osIssues and PRs related to the os subsystem.Issues and PRs related to the os subsystem.performanceIssues and PRs related to the performance of Node.js.Issues and PRs related to the performance of Node.js.
Description
- Version: 6.9.1 (also reproduced on 4.4.1 and 0.12.4)
- Platform: OS X, possibly others (Unable to repro on Linux but I believe it does happen but with less frequency)
- Subsystem: Cluster / IPC
When many IPC messages are sent between the master process and cluster workers, IPC channels to workers stop delivering messages. I have not been unable to restore working functionality of the workers and so they must be killed to resolve the issue. Since IPC has stopped working, simply using Worker.destroy() does not work since the method will wait for the disconnect event which never arrives (because of this issue).
I am able to repro on OS X by running the following script:
var cluster = require('cluster');
var express = require('express'); // tested with 4.14.0
const workerCount = 2;
const WTMIPC = 25;
const MTWIPC = 25;
if (cluster.isMaster) {
var workers = {}, worker;
for (var i = 0; i < workerCount; i++) {
worker = cluster.fork({});
workers[worker.process.pid] = worker;
}
var workerPongReceivedTime = {};
cluster.on('online', function(worker) {
worker.on('message', function(message) {
var currentTime = Date.now();
if (message.type === 'pong') {
workerPongReceivedTime[worker.process.pid] = currentTime;
console.log('received pong\tmaster-to-worker\t' + (message.timeReceived - message.timeSent) + '\tworker-to-master ' + (currentTime - message.timeSent));
} else if (message.type === 'fromEndpoint') {
for (var i = 0; i < MTWIPC; i++) {
worker.send({ type: 'toWorker' });
}
}
});
});
setInterval(function() {
var currentTime = Date.now();
console.log('sending ping');
Object.keys(workers).forEach(function(workerPid) {
workers[workerPid].send({ type: 'ping', time: Date.now() });
if (currentTime - workerPongReceivedTime[workerPid] > 10000) {
console.log('Worker missed pings: ' + workerPid);
}
});
}, 1000);
} else {
var app = express();
app.get('/test', function(req, res) {
for (i = 0; i < WTMIPC; i++) {
process.send({ type: 'fromEndpoint' });
}
res.send({ test: 123 });
});
app.listen(7080, function() {
console.log('server started');
});
process.on('message', function(message) {
if (message.type === 'ping') {
process.send({ type: 'pong', timeSent: message.time, timeReceived: Date.now() });
}
});
}
and using ApacheBench to place the server under load as follows:
ab -n 100000 -c 200 'http://localhost:7080/test'
I see the following, for example:
server started
server started
sending ping
received pong master-to-worker 1 worker-to-master 1
received pong master-to-worker 0 worker-to-master 1
sending ping
received pong master-to-worker 1 worker-to-master 3
received pong master-to-worker 19 worker-to-master 21
sending ping
received pong master-to-worker 2 worker-to-master 5
received pong master-to-worker 4 worker-to-master 7
sending ping
received pong master-to-worker 3 worker-to-master 4
received pong master-to-worker 4 worker-to-master 6
sending ping
received pong master-to-worker 9 worker-to-master 10
received pong master-to-worker 2 worker-to-master 10
sending ping
received pong master-to-worker 2 worker-to-master 4
received pong master-to-worker 4 worker-to-master 6
sending ping
received pong master-to-worker 2 worker-to-master 4
received pong master-to-worker 4 worker-to-master 6
... (about 10k - 60k requests later) ...
sending ping
sending ping
sending ping
sending ping
sending ping
sending ping
sending ping
sending ping
sending ping
sending ping
Worker missed pings: 97462
sending ping
Worker missed pings: 97462
Worker missed pings: 97463
sending ping
Worker missed pings: 97462
Worker missed pings: 97463
As I alluded to earlier, I have seen an issue on Linux which I believe is related but I have been so far unable to repro using this technique on Linux.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
child_processIssues and PRs related to the child_process subsystem.Issues and PRs related to the child_process subsystem.clusterIssues and PRs related to the cluster subsystem.Issues and PRs related to the cluster subsystem.freebsdIssues and PRs related to the FreeBSD platform.Issues and PRs related to the FreeBSD platform.macosIssues and PRs related to the macOS platform.Issues and PRs related to the macOS platform.osIssues and PRs related to the os subsystem.Issues and PRs related to the os subsystem.performanceIssues and PRs related to the performance of Node.js.Issues and PRs related to the performance of Node.js.
Activity
santigimeno commentedon Nov 20, 2016
What's ulimit value for
open files? You can see the value by executingulimit -aIt could be you're hitting that value. Does increasing that value help? See: http://superuser.com/a/303058
davidvetrano commentedon Nov 21, 2016
@santigimeno I'm able to repro with the hard and soft file descriptor limits set to 1M.
santigimeno commentedon Nov 22, 2016
@davidvetrano yes, I could reproduce the issue on
OS XandFreeBSD.What I have observed is that at some point the
masterdoesn't receive anNODE_HANDLE_ACKmessage in response to aNODE_HANDLEcommand sent to the workers causing that the ping messages are being stored in the child_process._handleQueueand they're never actually sent. In fact, the problem is that, for some reason, theNODE_HANDLEmessage is actually sent (apparently with success) to the workers, but the workers never receive it. I thought this was not possible in theIPCchannel as it's anAF_UNIXSOCK_STREAMconnection. Any idea why this could be happening?/cc @bnoordhuis
pidgeonman commentedon Dec 13, 2016
I lose IPC communication on Linux too, having 41 workers and rather heavy DB access on each. No error is given on console.
rooftopsparrow commentedon Jan 25, 2017
Update: After testing again, v6.1 does in fact have has the bug. Please disregard.
I've taken the above snippet and did a manual "git bisect" on all minor versions on macOS (10.12.2) and the behavior is not exhibited on v6.1.0 but is introduced in ^v6.2.0, and is still prevalent in v7.I'm currently running a real git bisect on the commits between v6.1 and v6.2 to hopefully identify the commit where this regression took place.santigimeno commentedon Jan 28, 2017
Yeah, I have also reproduced it in
4.7.1.Trott commentedon Jul 16, 2017
@santigimeno This should remain open?
/cc @bnoordhuis, @cjihrig, @mcollina
bnoordhuis commentedon Jul 17, 2017
@santigimeno #13235 fixed this, didn't it?
That PR is on track for v6.x and it seems reasonable to me to also target v4.x since it's a rather insidious bug.
santigimeno commentedon Jul 17, 2017
I think so. I still can reproduce it with current master on
FreeBSDsantigimeno commentedon Jul 17, 2017
@bnoordhuis sorry I hadn't read your comment before answering...
I had forgotten about this one but it certainly looks like it could have been solved by #13235, but from a quick check it doesn't look it's solved so it may be a different issue.
child_process: fix handleless NODE_HANDLE handling
20 remaining items
pitaj commentedon May 21, 2018
@gireeshpunathil I'm on Windows, and it seems like your comment is mostly about Linux?
gireeshpunathil commentedon May 22, 2018
@pitaj, thanks. I was following the code and the platform from the original postings.
Are you using the same code on Windows, or something different? if so, please pass it on. Also, what is the observation - similar to mac os, same as mac os, or different?
I too tested in windows, and I got some surprising result (certain tunings to the original test,and we get complete hang!) . We need to separate that issue from this, so let me hear from you.
gireeshpunathil commentedon May 22, 2018
looked at the windows hang, and understood the reason too.
every time a client connects, 25 messages (
fromEndpoint) go from the node to the master.every time the master receives a message of that type, it sends 25 messages back (625 messages per worker)
So depending on the number of concurrent requests, performance can really vary, and the dependancy between the requests and the latency is exponential (s you already observed earlier):
There is nothing Windows specific issue observed here from Node.js perspective, other than potential difference in the system configuration / resources. So my original proposal on horizontal scaling stands.
gireeshpunathil commentedon May 29, 2018
Not a Node.js bug, closing. Exponential stress in the tcp layer causes process to slow down, suggested to share work between multiple hosts.
pitaj commentedon May 29, 2018
@gireeshpunathil I have some repro code that doesn't use any TCP AFAIK, unless the IPC channel uses TCP itself.
I can throw that up on a gist later today.
pitaj commentedon May 30, 2018
https://gist.github.com/pitaj/e8463a023847697e72e7f01ac8ed2fd6
gireeshpunathil commentedon May 31, 2018
Thanks @pitaj for the repro. Turns out that the windows issue is unrelated (compelte hang) to the originally posted issue (slow response) on macos and family.
I am able to reproduce the hang. Looking at multiple dumps, I see that the main thread of different processes (including the master) are engaged in:
this is a known issue with libuv where multiple parties attempt to write to the same pipe, from either sides, under rare situations.
#7657 posted this originally, and libuv/libuv#1843 fixed it recently. It will be sometime before Node.js consumes it.
deps: upgrade to libuv 1.21.0