1
1
import net from 'net'
2
- import { AbortError , AlreadyStartedError , InvalidParametersError , NotStartedError , TypedEventEmitter } from '@libp2p/interface'
2
+ import { AlreadyStartedError , InvalidParametersError , NotStartedError , TypedEventEmitter , setMaxListeners } from '@libp2p/interface'
3
+ import { pEvent } from 'p-event'
3
4
import { CODE_P2P } from './constants.js'
4
5
import { toMultiaddrConnection } from './socket-to-conn.js'
5
6
import {
@@ -67,19 +68,23 @@ type Status = { code: TCPListenerStatusCode.INACTIVE } | {
67
68
68
69
export class TCPListener extends TypedEventEmitter < ListenerEvents > implements Listener {
69
70
private readonly server : net . Server
70
- /** Keep track of open connections to destroy in case of timeout */
71
- private readonly connections = new Set < MultiaddrConnection > ( )
71
+ /** Keep track of open sockets to destroy in case of timeout */
72
+ private readonly sockets = new Set < net . Socket > ( )
72
73
private status : Status = { code : TCPListenerStatusCode . INACTIVE }
73
74
private metrics ?: TCPListenerMetrics
74
75
private addr : string
75
76
private readonly log : Logger
77
+ private readonly shutdownController : AbortController
76
78
77
79
constructor ( private readonly context : Context ) {
78
80
super ( )
79
81
80
82
context . keepAlive = context . keepAlive ?? true
81
83
context . noDelay = context . noDelay ?? true
82
84
85
+ this . shutdownController = new AbortController ( )
86
+ setMaxListeners ( Infinity , this . shutdownController . signal )
87
+
83
88
this . log = context . logger . forComponent ( 'libp2p:tcp:listener' )
84
89
this . addr = 'unknown'
85
90
this . server = net . createServer ( context , this . onSocket . bind ( this ) )
@@ -119,7 +124,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
119
124
help : 'Current active connections in TCP listener' ,
120
125
calculate : ( ) => {
121
126
return {
122
- [ this . addr ] : this . connections . size
127
+ [ this . addr ] : this . sockets . size
123
128
}
124
129
}
125
130
} )
@@ -195,18 +200,20 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
195
200
}
196
201
197
202
this . log ( 'new inbound connection %s' , maConn . remoteAddr )
203
+ this . sockets . add ( socket )
198
204
199
- this . context . upgrader . upgradeInbound ( maConn )
205
+ this . context . upgrader . upgradeInbound ( maConn , {
206
+ signal : this . shutdownController . signal
207
+ } )
200
208
. then ( ( conn ) => {
201
209
this . log ( 'inbound connection upgraded %s' , maConn . remoteAddr )
202
- this . connections . add ( maConn )
203
210
204
211
socket . once ( 'close' , ( ) => {
205
- this . connections . delete ( maConn )
212
+ this . sockets . delete ( socket )
206
213
207
214
if (
208
215
this . context . closeServerOnMaxConnections != null &&
209
- this . connections . size < this . context . closeServerOnMaxConnections . listenBelow
216
+ this . sockets . size < this . context . closeServerOnMaxConnections . listenBelow
210
217
) {
211
218
// The most likely case of error is if the port taken by this
212
219
// application is bound by another process during the time the
@@ -227,18 +234,17 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
227
234
228
235
if (
229
236
this . context . closeServerOnMaxConnections != null &&
230
- this . connections . size >= this . context . closeServerOnMaxConnections . closeAbove
237
+ this . sockets . size >= this . context . closeServerOnMaxConnections . closeAbove
231
238
) {
232
- this . pause ( false ) . catch ( e => {
233
- this . log . error ( 'error attempting to close server once connection count over limit' , e )
234
- } )
239
+ this . pause ( )
235
240
}
236
241
237
242
this . safeDispatchEvent ( 'connection' , { detail : conn } )
238
243
} )
239
244
. catch ( async err => {
240
245
this . log . error ( 'inbound connection upgrade failed' , err )
241
246
this . metrics ?. errors . increment ( { [ `${ this . addr } inbound_upgrade` ] : true } )
247
+ this . sockets . delete ( socket )
242
248
maConn . abort ( err )
243
249
} )
244
250
}
@@ -300,15 +306,28 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
300
306
}
301
307
302
308
async close ( ) : Promise < void > {
303
- const err = new AbortError ( 'Listener is closing' )
309
+ const events : Array < Promise < void > > = [ ]
304
310
305
- // synchronously close each connection
306
- this . connections . forEach ( conn => {
307
- conn . abort ( err )
308
- } )
311
+ if ( this . server . listening ) {
312
+ events . push ( pEvent ( this . server , 'close' ) )
313
+ }
309
314
310
315
// shut down the server socket, permanently
311
- await this . pause ( true )
316
+ this . pause ( true )
317
+
318
+ // stop any in-progress connection upgrades
319
+ this . shutdownController . abort ( )
320
+
321
+ // synchronously close any open connections - should be done after closing
322
+ // the server socket in case new sockets are opened during the shutdown
323
+ this . sockets . forEach ( socket => {
324
+ if ( socket . readable ) {
325
+ events . push ( pEvent ( socket , 'close' ) )
326
+ socket . destroy ( )
327
+ }
328
+ } )
329
+
330
+ await Promise . all ( events )
312
331
}
313
332
314
333
/**
@@ -332,7 +351,7 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
332
351
this . log ( 'listening on %s' , this . server . address ( ) )
333
352
}
334
353
335
- private async pause ( permanent : boolean ) : Promise < void > {
354
+ private pause ( permanent : boolean = false ) : void {
336
355
if ( ! this . server . listening && this . status . code === TCPListenerStatusCode . PAUSED && permanent ) {
337
356
this . status = { code : TCPListenerStatusCode . INACTIVE }
338
357
return
@@ -361,15 +380,10 @@ export class TCPListener extends TypedEventEmitter<ListenerEvents> implements Li
361
380
// during the time the server is closing
362
381
this . status = permanent ? { code : TCPListenerStatusCode . INACTIVE } : { ...this . status , code : TCPListenerStatusCode . PAUSED }
363
382
364
- await new Promise < void > ( ( resolve , reject ) => {
365
- this . server . close ( err => {
366
- if ( err != null ) {
367
- reject ( err )
368
- return
369
- }
370
-
371
- resolve ( )
372
- } )
373
- } )
383
+ // stop accepting incoming connections - existing connections are maintained
384
+ // - any callback passed here would be invoked after existing connections
385
+ // close, we want to maintain them so no callback is passed otherwise his
386
+ // method will never return
387
+ this . server . close ( )
374
388
}
375
389
}
0 commit comments