2222'use strict' ;
2323
2424const {
25+ NumberIsNaN,
2526 ObjectKeys,
2627 ObjectSetPrototypeOf,
2728 ObjectValues,
@@ -36,11 +37,14 @@ const {
3637 codes : {
3738 ERR_INVALID_ARG_TYPE ,
3839 ERR_INVALID_OPT_VALUE ,
40+ ERR_OUT_OF_RANGE ,
3941 } ,
4042} = require ( 'internal/errors' ) ;
4143const { once } = require ( 'internal/util' ) ;
44+ const { validateNumber } = require ( 'internal/validators' ) ;
4245
4346const kOnKeylog = Symbol ( 'onkeylog' ) ;
47+ const kRequestOptions = Symbol ( 'requestOptions' ) ;
4448// New Agent code.
4549
4650// The largest departure from the previous implementation is that
@@ -88,11 +92,22 @@ function Agent(options) {
8892 this . maxSockets = this . options . maxSockets || Agent . defaultMaxSockets ;
8993 this . maxFreeSockets = this . options . maxFreeSockets || 256 ;
9094 this . scheduling = this . options . scheduling || 'fifo' ;
95+ this . maxTotalSockets = this . options . maxTotalSockets ;
96+ this . totalSocketCount = 0 ;
9197
9298 if ( this . scheduling !== 'fifo' && this . scheduling !== 'lifo' ) {
9399 throw new ERR_INVALID_OPT_VALUE ( 'scheduling' , this . scheduling ) ;
94100 }
95101
102+ if ( this . maxTotalSockets !== undefined ) {
103+ validateNumber ( this . maxTotalSockets , 'maxTotalSockets' ) ;
104+ if ( this . maxTotalSockets <= 0 || NumberIsNaN ( this . maxTotalSockets ) )
105+ throw new ERR_OUT_OF_RANGE ( 'maxTotalSockets' , '> 0' ,
106+ this . maxTotalSockets ) ;
107+ } else {
108+ this . maxTotalSockets = Infinity ;
109+ }
110+
96111 this . on ( 'free' , ( socket , options ) => {
97112 const name = this . getName ( options ) ;
98113 debug ( 'agent.on(free)' , name ) ;
@@ -131,7 +146,8 @@ function Agent(options) {
131146 if ( this . sockets [ name ] )
132147 count += this . sockets [ name ] . length ;
133148
134- if ( count > this . maxSockets ||
149+ if ( this . totalSocketCount > this . maxTotalSockets ||
150+ count > this . maxSockets ||
135151 freeLen >= this . maxFreeSockets ||
136152 ! this . keepSocketAlive ( socket ) ) {
137153 socket . destroy ( ) ;
@@ -246,7 +262,9 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
246262 this . reuseSocket ( socket , req ) ;
247263 setRequestSocket ( this , req , socket ) ;
248264 this . sockets [ name ] . push ( socket ) ;
249- } else if ( sockLen < this . maxSockets ) {
265+ this . totalSocketCount ++ ;
266+ } else if ( sockLen < this . maxSockets &&
267+ this . totalSocketCount < this . maxTotalSockets ) {
250268 debug ( 'call onSocket' , sockLen , freeLen ) ;
251269 // If we are under maxSockets create a new one.
252270 this . createSocket ( req , options , ( err , socket ) => {
@@ -261,6 +279,10 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
261279 if ( ! this . requests [ name ] ) {
262280 this . requests [ name ] = [ ] ;
263281 }
282+
283+ // Used to create sockets for pending requests from different origin
284+ req [ kRequestOptions ] = options ;
285+
264286 this . requests [ name ] . push ( req ) ;
265287 }
266288} ;
@@ -286,7 +308,8 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
286308 this . sockets [ name ] = [ ] ;
287309 }
288310 this . sockets [ name ] . push ( s ) ;
289- debug ( 'sockets' , name , this . sockets [ name ] . length ) ;
311+ this . totalSocketCount ++ ;
312+ debug ( 'sockets' , name , this . sockets [ name ] . length , this . totalSocketCount ) ;
290313 installListeners ( this , s , options ) ;
291314 cb ( null , s ) ;
292315 } ) ;
@@ -392,13 +415,33 @@ Agent.prototype.removeSocket = function removeSocket(s, options) {
392415 // Don't leak
393416 if ( sockets [ name ] . length === 0 )
394417 delete sockets [ name ] ;
418+ this . totalSocketCount -- ;
395419 }
396420 }
397421 }
398422
423+ let req ;
399424 if ( this . requests [ name ] && this . requests [ name ] . length ) {
400425 debug ( 'removeSocket, have a request, make a socket' ) ;
401- const req = this . requests [ name ] [ 0 ] ;
426+ req = this . requests [ name ] [ 0 ] ;
427+ } else {
428+ // TODO(rickyes): this logic will not be FIFO across origins.
429+ // There might be older requests in a different origin, but
430+ // if the origin which releases the socket has pending requests
431+ // that will be prioritized.
432+ for ( const prop in this . requests ) {
433+ // Check whether this specific origin is already at maxSockets
434+ if ( this . sockets [ prop ] && this . sockets [ prop ] . length ) break ;
435+ debug ( 'removeSocket, have a request with different origin,' +
436+ ' make a socket' ) ;
437+ req = this . requests [ prop ] [ 0 ] ;
438+ options = req [ kRequestOptions ] ;
439+ break ;
440+ }
441+ }
442+
443+ if ( req && options ) {
444+ req [ kRequestOptions ] = undefined ;
402445 // If we have pending requests and a socket gets closed make a new one
403446 this . createSocket ( req , options , ( err , socket ) => {
404447 if ( err )
@@ -407,6 +450,7 @@ Agent.prototype.removeSocket = function removeSocket(s, options) {
407450 socket . emit ( 'free' ) ;
408451 } ) ;
409452 }
453+
410454} ;
411455
412456Agent . prototype . keepSocketAlive = function keepSocketAlive ( socket ) {
0 commit comments