diff --git a/lib/_http_agent.js b/lib/_http_agent.js index f973c34425..8309264685 100644 --- a/lib/_http_agent.js +++ b/lib/_http_agent.js @@ -59,6 +59,7 @@ function Agent(options) { self.keepAliveMsecs = self.options.keepAliveMsecs || 1000; self.keepAlive = self.options.keepAlive || false; self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets; + self.maxFreeSockets = self.options.maxFreeSockets || 256; self.on('free', function(socket, options) { var name = self.getName(options); @@ -80,11 +81,13 @@ function Agent(options) { !socket.destroyed && self.options.keepAlive) { var freeSockets = self.freeSockets[name]; - var count = freeSockets ? freeSockets.length : 0; + var freeLen = freeSockets ? freeSockets.length : 0; + var count = freeLen; if (self.sockets[name]) count += self.sockets[name].length; - if (count > self.maxSockets) { + if (count >= self.maxSockets || freeLen >= self.maxFreeSockets) { + self.removeSocket(socket, options); socket.destroy(); } else { freeSockets = freeSockets || []; @@ -92,9 +95,11 @@ function Agent(options) { socket.setKeepAlive(true, self.keepAliveMsecs); socket.unref(); socket._httpMessage = null; + self.removeSocket(socket, options); freeSockets.push(socket); } } else { + self.removeSocket(socket, options); socket.destroy(); } } @@ -146,10 +151,13 @@ Agent.prototype.addRequest = function(req, options) { this.sockets[name] = []; } - if (this.freeSockets[name] && this.freeSockets[name].length) { - debug('have free socket'); + var freeLen = this.freeSockets[name] ? this.freeSockets[name].length : 0; + var sockLen = freeLen + this.sockets[name].length; + + if (freeLen) { // we have a free socket, so use that. var socket = this.freeSockets[name].shift(); + debug('have free socket'); // don't leak if (!this.freeSockets[name].length) @@ -157,8 +165,9 @@ Agent.prototype.addRequest = function(req, options) { socket.ref(); req.onSocket(socket); - } else if (this.sockets[name].length < this.maxSockets) { - debug('call onSocket'); + this.sockets[name].push(socket); + } else if (sockLen < this.maxSockets) { + debug('call onSocket', sockLen, freeLen); // If we are under maxSockets create a new one. req.onSocket(this.createSocket(req, options)); } else { diff --git a/test/simple/test-http-keepalive-maxsockets.js b/test/simple/test-http-keepalive-maxsockets.js new file mode 100644 index 0000000000..00e3bc7daa --- /dev/null +++ b/test/simple/test-http-keepalive-maxsockets.js @@ -0,0 +1,102 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); + +var http = require('http'); + + +var serverSockets = []; +var server = http.createServer(function(req, res) { + if (serverSockets.indexOf(req.socket) === -1) { + serverSockets.push(req.socket); + } + res.end(req.url); +}); +server.listen(common.PORT); + +var agent = http.Agent({ + keepAlive: true, + maxSockets: 5, + maxFreeSockets: 2 +}); + +// make 10 requests in parallel, +// then 10 more when they all finish. +function makeReqs(n, cb) { + for (var i = 0; i < n; i++) + makeReq(i, then) + + function then(er) { + if (er) + return cb(er); + else if (--n === 0) + setTimeout(cb); + } +} + +function makeReq(i, cb) { + agent.request({ port: common.PORT, path: '/' + i }, function(res) { + var data = ''; + res.setEncoding('ascii'); + res.on('data', function(c) { + data += c; + }); + res.on('end', function() { + assert.equal(data, '/' + i); + cb(); + }); + }).end(); +} + +var closed = false; +makeReqs(10, function(er) { + assert.ifError(er); + assert.equal(count(agent.freeSockets), 2); + assert.equal(count(agent.sockets), 0); + assert.equal(serverSockets.length, 5); + + // now make 10 more reqs. + // should use the 2 free reqs from the pool first. + makeReqs(10, function(er) { + assert.ifError(er); + assert.equal(count(agent.freeSockets), 2); + assert.equal(count(agent.sockets), 0); + assert.equal(serverSockets.length, 8); + + agent.destroy(); + server.close(function() { + closed = true; + }); + }); +}); + +function count(sockets) { + return Object.keys(sockets).reduce(function(n, name) { + return n + sockets[name].length; + }, 0); +} + +process.on('exit', function() { + assert(closed); + console.log('ok'); +});