Implement net.Server.maxConnections
Simplify EMFILE behavior.
This commit is contained in:
parent
cde80d9859
commit
4593c04959
@ -2196,6 +2196,15 @@ Stops the server from accepting new connections. This function is
|
|||||||
asynchronous, the server is finally closed when the server emits a `'close'`
|
asynchronous, the server is finally closed when the server emits a `'close'`
|
||||||
event.
|
event.
|
||||||
|
|
||||||
|
### server.maxConnections
|
||||||
|
|
||||||
|
Set this property to reject connections when the server's connection count gets high.
|
||||||
|
|
||||||
|
### server.connections
|
||||||
|
|
||||||
|
The number of concurrent connections on the server.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## net.Stream
|
## net.Stream
|
||||||
|
|
||||||
|
47
lib/net.js
47
lib/net.js
@ -220,29 +220,6 @@ var ioWatchers = new FreeList("iowatcher", 100, function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// waitingForFDs stores servers which have experienced EMFILE.
|
|
||||||
// When a file descriptor becomes available through closeFD()
|
|
||||||
// a server from waitingForFDs is started.
|
|
||||||
|
|
||||||
var waitingForFDs = [];
|
|
||||||
|
|
||||||
function closeFD(fd) {
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
// Try to recover from EMFILE
|
|
||||||
|
|
||||||
var server, serverFD;
|
|
||||||
while (server = waitingForFDs.shift()) {
|
|
||||||
serverFD = parseInt(server.fd);
|
|
||||||
if (serverFD && serverFD > 0) {
|
|
||||||
server.watcher.set(serverFD, true, false);
|
|
||||||
server.watcher.start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Allocated on demand.
|
// Allocated on demand.
|
||||||
var pool = null;
|
var pool = null;
|
||||||
function allocNewPool () {
|
function allocNewPool () {
|
||||||
@ -997,9 +974,13 @@ Stream.prototype.destroy = function (exception) {
|
|||||||
this.secureStream.close();
|
this.secureStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.server) {
|
||||||
|
this.server.connections--;
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME Bug when this.fd == 0
|
// FIXME Bug when this.fd == 0
|
||||||
if (typeof this.fd == 'number') {
|
if (typeof this.fd == 'number') {
|
||||||
closeFD(this.fd);
|
close(this.fd);
|
||||||
this.fd = null;
|
this.fd = null;
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
if (exception) self.emit('error', exception);
|
if (exception) self.emit('error', exception);
|
||||||
@ -1058,6 +1039,8 @@ function Server (listener) {
|
|||||||
self.addListener('connection', listener);
|
self.addListener('connection', listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.connections = 0;
|
||||||
|
|
||||||
self.watcher = new IOWatcher();
|
self.watcher = new IOWatcher();
|
||||||
self.watcher.host = self;
|
self.watcher.host = self;
|
||||||
self.watcher.callback = function () {
|
self.watcher.callback = function () {
|
||||||
@ -1065,15 +1048,19 @@ function Server (listener) {
|
|||||||
try {
|
try {
|
||||||
var peerInfo = accept(self.fd);
|
var peerInfo = accept(self.fd);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.errno == EMFILE) {
|
if (e.errno == EMFILE) return;
|
||||||
waitingForFDs.push(self);
|
|
||||||
self.watcher.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
if (!peerInfo) return;
|
if (!peerInfo) return;
|
||||||
|
|
||||||
|
if (self.maxConnections && self.connections >= self.maxConnections) {
|
||||||
|
// Accept and close the connection.
|
||||||
|
close(peerInfo.fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.connections++;
|
||||||
|
|
||||||
var s = new Stream(peerInfo.fd, self.type);
|
var s = new Stream(peerInfo.fd, self.type);
|
||||||
s.remoteAddress = peerInfo.address;
|
s.remoteAddress = peerInfo.address;
|
||||||
s.remotePort = peerInfo.port;
|
s.remotePort = peerInfo.port;
|
||||||
@ -1209,7 +1196,7 @@ Server.prototype.close = function () {
|
|||||||
|
|
||||||
self.watcher.stop();
|
self.watcher.stop();
|
||||||
|
|
||||||
closeFD(self.fd);
|
close(self.fd);
|
||||||
self.fd = null;
|
self.fd = null;
|
||||||
|
|
||||||
if (self.type === "unix") {
|
if (self.type === "unix") {
|
||||||
|
@ -13,6 +13,7 @@ function pingPongTest (port, host) {
|
|||||||
var server = net.createServer(function (socket) {
|
var server = net.createServer(function (socket) {
|
||||||
console.log("connection: " + socket.remoteAddress);
|
console.log("connection: " + socket.remoteAddress);
|
||||||
assert.equal(server, socket.server);
|
assert.equal(server, socket.server);
|
||||||
|
assert.equal(1, server.connections);
|
||||||
|
|
||||||
socket.setNoDelay();
|
socket.setNoDelay();
|
||||||
socket.timeout = 0;
|
socket.timeout = 0;
|
||||||
|
79
test/simple/test-net-server-max-connections.js
Normal file
79
test/simple/test-net-server-max-connections.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
common = require("../common");
|
||||||
|
assert = common.assert;
|
||||||
|
net = require('net');
|
||||||
|
|
||||||
|
// This test creates 200 connections to a server and sets the server's
|
||||||
|
// maxConnections property to 100. The first 100 connections make it through
|
||||||
|
// and the last 100 connections are rejected.
|
||||||
|
// TODO: test that the server can accept more connections after it reaches
|
||||||
|
// its maximum and some are closed.
|
||||||
|
|
||||||
|
N = 200;
|
||||||
|
count = 0;
|
||||||
|
closes = 0;
|
||||||
|
waits = [];
|
||||||
|
|
||||||
|
server = net.createServer(function (connection) {
|
||||||
|
console.error("connect %d", count++);
|
||||||
|
connection.write("hello");
|
||||||
|
waits.push(function () { connection.end(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(common.PORT, function () {
|
||||||
|
for (var i = 0; i < N; i++) {
|
||||||
|
makeConnection(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.maxConnections = N/2;
|
||||||
|
|
||||||
|
console.error("server.maxConnections = %d", server.maxConnections);
|
||||||
|
|
||||||
|
|
||||||
|
function makeConnection (index) {
|
||||||
|
setTimeout(function () {
|
||||||
|
var c = net.createConnection(common.PORT);
|
||||||
|
var gotData = false;
|
||||||
|
|
||||||
|
c.on('end', function () { c.end(); });
|
||||||
|
|
||||||
|
c.on('data', function (b) {
|
||||||
|
gotData = true;
|
||||||
|
assert.ok(0 < b.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
c.on('error', function (e) {
|
||||||
|
console.error("error %d: %s", index, e);
|
||||||
|
});
|
||||||
|
|
||||||
|
c.on('close', function () {
|
||||||
|
console.error("closed %d", index);
|
||||||
|
closes++;
|
||||||
|
|
||||||
|
if (closes < N/2) {
|
||||||
|
assert.ok(server.maxConnections <= index,
|
||||||
|
index + " was one of the first closed connections but shouldnt have been");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closes === N/2) {
|
||||||
|
var cb;
|
||||||
|
console.error("calling wait callback.");
|
||||||
|
while (cb = waits.shift()) {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < server.maxConnections) {
|
||||||
|
assert.equal(true, gotData, index + " didn't get data, but should have");
|
||||||
|
} else {
|
||||||
|
assert.equal(false, gotData, index + " got data, but shouldn't have");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
process.on('exit', function () {
|
||||||
|
assert.equal(N, closes);
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user