@@ -376,6 +376,13 @@ function closeSocketHandle(self, isException, isCleanupPending = false) {
376376const kBytesRead = Symbol ( 'kBytesRead' ) ;
377377const kBytesWritten = Symbol ( 'kBytesWritten' ) ;
378378const kSetTOS = Symbol ( 'kSetTOS' ) ;
379+ // Generation token for a Socket's connection lifecycle. Async work started by
380+ // a connect() attempt (e.g. a DNS lookup) captures the current generation; the
381+ // generation is advanced once per lifecycle, in _destroy, so stale async work
382+ // from a destroyed lifecycle cannot drive a newer lifecycle established by a
383+ // later connect() (e.g. reconnect after 'close'). Multiple connect() calls
384+ // within one undestroyed lifecycle share a generation.
385+ const kConnectGeneration = Symbol ( 'kConnectGeneration' ) ;
379386// Marks a Socket whose handle is an adopted, already-bound BoundSocket.
380387const kBoundSource = Symbol ( 'kBoundSource' ) ;
381388
@@ -1100,6 +1107,9 @@ Socket.prototype._destroy = function(exception, cb) {
11001107 debug ( 'destroy' ) ;
11011108
11021109 this . connecting = false ;
1110+ // The connection lifecycle ends here; invalidate async work (e.g. a DNS
1111+ // lookup) still in flight so it cannot drive a newer lifecycle.
1112+ this [ kConnectGeneration ] = ( this [ kConnectGeneration ] || 0 ) + 1 ;
11031113
11041114 for ( let s = this ; s !== null ; s = s . _parent ) {
11051115 clearTimeout ( s [ kTimeout ] ) ;
@@ -1415,6 +1425,13 @@ function internalConnectMultiple(context, canceled) {
14151425 clearTimeout ( context [ kTimeout ] ) ;
14161426 const self = context . socket ;
14171427
1428+ // The connection lifecycle that created this context may have been destroyed
1429+ // and a newer connect() established; a stale context must not drive the new
1430+ // lifecycle's handle.
1431+ if ( self [ kConnectGeneration ] !== context . connectGeneration ) {
1432+ return ;
1433+ }
1434+
14181435 // We were requested to abort. Stop all operations
14191436 if ( self . _aborted ) {
14201437 return ;
@@ -1778,14 +1795,18 @@ function lookupAndConnect(self, options) {
17781795 return ;
17791796 }
17801797
1798+ const connectGeneration = self [ kConnectGeneration ] ;
1799+
17811800 defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , function ( ) {
17821801 lookup ( host , dnsopts , function emitLookup ( err , ip , addressType ) {
17831802 self . emit ( 'lookup' , err , ip , addressType , host ) ;
17841803
17851804 // It's possible we were destroyed while looking this up.
17861805 // XXX it would be great if we could cancel the promise returned by
17871806 // the look up.
1788- if ( ! self . connecting ) return ;
1807+ // A stale callback may also arrive after the attempt was destroyed and a
1808+ // newer connect() attempt was started; it must not drive the new attempt.
1809+ if ( ! self . connecting || self [ kConnectGeneration ] !== connectGeneration ) return ;
17891810
17901811 if ( err ) {
17911812 // net.createConnection() creates a net.Socket object and immediately
@@ -1815,12 +1836,16 @@ function lookupAndConnect(self, options) {
18151836function lookupAndConnectMultiple (
18161837 self , async_id_symbol , lookup , host , options , dnsopts , port , localAddress , localPort , timeout ,
18171838) {
1839+ const connectGeneration = self [ kConnectGeneration ] ;
1840+
18181841 defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , function emitLookup ( ) {
18191842 lookup ( host , dnsopts , function emitLookup ( err , addresses ) {
18201843 // It's possible we were destroyed while looking this up.
18211844 // XXX it would be great if we could cancel the promise returned by
18221845 // the look up.
1823- if ( ! self . connecting ) {
1846+ // A stale callback may also arrive after the attempt was destroyed and a
1847+ // newer connect() attempt was started; it must not drive the new attempt.
1848+ if ( ! self . connecting || self [ kConnectGeneration ] !== connectGeneration ) {
18241849 return ;
18251850 } else if ( err ) {
18261851 self . emit ( 'lookup' , err , undefined , undefined , host ) ;
@@ -1843,7 +1868,7 @@ function lookupAndConnectMultiple(
18431868 const { address : ip , family : addressType } = address ;
18441869 self . emit ( 'lookup' , err , ip , addressType , host ) ;
18451870 // It's possible we were destroyed while looking this up.
1846- if ( ! self . connecting ) {
1871+ if ( ! self . connecting || self [ kConnectGeneration ] !== connectGeneration ) {
18471872 return ;
18481873 }
18491874 if ( isIP ( ip ) && ( addressType === 4 || addressType === 6 ) ) {
@@ -1914,6 +1939,7 @@ function lookupAndConnectMultiple(
19141939 socket : self ,
19151940 addresses : toAttempt ,
19161941 current : 0 ,
1942+ connectGeneration,
19171943 port,
19181944 localPort,
19191945 timeout,
@@ -2087,6 +2113,14 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
20872113
20882114 const self = context . socket ;
20892115
2116+ // The connection lifecycle that created this context may have been destroyed
2117+ // and a newer connect() established; drop the stale completion, closing the
2118+ // completed handle so it cannot leak.
2119+ if ( self [ kConnectGeneration ] !== context . connectGeneration ) {
2120+ handle . close ( ) ;
2121+ return ;
2122+ }
2123+
20902124 // Some error occurred, add to the list of exceptions
20912125 if ( status !== 0 ) {
20922126 const ex = createConnectionError ( req , status ) ;
@@ -2115,6 +2149,14 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
21152149
21162150function internalConnectMultipleTimeout ( context , req , handle ) {
21172151 debug ( 'connect/multiple: connection to %s:%s timed out' , req . address , req . port ) ;
2152+
2153+ // The connection lifecycle that created this context may have been destroyed
2154+ // and a newer connect() established; drop the stale timeout so it cannot
2155+ // emit a spurious connectionAttemptTimeout or drive further attempts.
2156+ if ( context . socket [ kConnectGeneration ] !== context . connectGeneration ) {
2157+ return ;
2158+ }
2159+
21182160 context . socket . emit ( 'connectionAttemptTimeout' , req . address , req . port , req . addressType ) ;
21192161
21202162 req . oncomplete = undefined ;
0 commit comments