Browse Source

Gracefully handle EMFILE

Implementing a tip from Marc Lehmann:
http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#The_special_problem_of_accept_ing_wh

Keep an extra FD around for every server. When you hit EMFILE, destroy that
FD, accept a connection, close it; in this way you can clear the connection
queue and let people know that you're overload.

No more timeout needed.
v0.7.4-release
Ryan Dahl 14 years ago
parent
commit
ac54272218
  1. 5
      benchmark/idle_clients.js
  2. 61
      lib/net.js

5
benchmark/idle_clients.js

@ -17,7 +17,7 @@ function connect () {
s.on('close', function () { s.on('close', function () {
if (gotConnected) connections--; if (gotConnected) connections--;
if (!haderror) connect(); //if (!haderror) connect();
}); });
s.on('end', function () { s.on('end', function () {
@ -36,6 +36,9 @@ connect();
var oldConnections, oldErrors; var oldConnections, oldErrors;
// Try to start new connections every so often
setInterval(connect, 5000);
setInterval(function () { setInterval(function () {
if (oldConnections != connections) { if (oldConnections != connections) {
oldConnections = connections; oldConnections = connections;

61
lib/net.js

@ -922,36 +922,38 @@ function Server (/* [ options, ] listener */) {
self.connections = 0; self.connections = 0;
self.paused = false;
self.pauseTimeout = 1000;
self.allowHalfOpen = options.allowHalfOpen || false; self.allowHalfOpen = options.allowHalfOpen || false;
function pause () {
// We've hit the maximum file limit. What to do?
// Let's try again in 1 second.
self.watcher.stop();
// If we're already paused, don't do another timeout.
if (self.paused) return;
setTimeout(function () {
self.paused = false;
// Make sure we haven't closed in the interim
if (typeof self.fd != 'number') return;
self.watcher.start();
}, self.pauseTimeout);
}
self.watcher = new IOWatcher(); self.watcher = new IOWatcher();
self.watcher.host = self; self.watcher.host = self;
self.watcher.callback = function () { self.watcher.callback = function () {
// Just in case we don't have a dummy fd.
if (!self._dummyFD) self._getDummyFD();
while (self.fd) { while (self.fd) {
try { try {
var peerInfo = accept(self.fd); var peerInfo = accept(self.fd);
} catch (e) { } catch (e) {
if (e.errno == EMFILE) { if (e.errno == EMFILE) {
pause(); // Output a warning, but only at most every 5 seconds.
var now = new Date();
if (now - self._lastEMFILEWarning > 5000) {
console.error("(node) Hit max file limit. Increase 'ulimit -n'.");
}
self._lastEMFILEWarning = now;
// Gracefully reject pending clients by freeing up a file
// descriptor.
if (self._dummyFD) {
close(self._dummyFD);
self._dummyFD = null;
while (true) {
peerInfo = accept(self.fd);
if (!peerInfo) break;
close(peerInfo.fd);
}
self._getDummyFD();
}
return; return;
} }
throw e; throw e;
@ -1076,9 +1078,23 @@ Server.prototype._startWatcher = function () {
this.emit("listening"); this.emit("listening");
}; };
Server.prototype._getDummyFD = function () {
try {
this._dummyFD = socket("tcp");
} catch (e) {
this._dummyFD = null;
}
};
Server.prototype._doListen = function () { Server.prototype._doListen = function () {
var self = this; var self = this;
// Grab a dummy fd for EMFILE conditions.
self._getDummyFD();
self._lastEMFILEWarning = 0;
try { try {
bind(self.fd, arguments[0], arguments[1]); bind(self.fd, arguments[0], arguments[1]);
} catch (err) { } catch (err) {
@ -1120,6 +1136,11 @@ Server.prototype.close = function () {
close(self.fd); close(self.fd);
self.fd = null; self.fd = null;
if (this._dummyFD) {
close(this._dummyFD);
this._dummyFD = null;
}
if (self.type === "unix") { if (self.type === "unix") {
fs.unlink(self.path, function () { fs.unlink(self.path, function () {
self.emit("close"); self.emit("close");

Loading…
Cancel
Save