@@ -13,6 +13,7 @@ import {
13
13
CONNECTION_POOL_CLEARED ,
14
14
CONNECTION_POOL_CLOSED ,
15
15
CONNECTION_POOL_CREATED ,
16
+ CONNECTION_POOL_READY ,
16
17
CONNECTION_READY
17
18
} from '../constants' ;
18
19
import { MongoError , MongoInvalidArgumentError , MongoRuntimeError } from '../error' ;
@@ -31,9 +32,10 @@ import {
31
32
ConnectionPoolClearedEvent ,
32
33
ConnectionPoolClosedEvent ,
33
34
ConnectionPoolCreatedEvent ,
35
+ ConnectionPoolReadyEvent ,
34
36
ConnectionReadyEvent
35
37
} from './connection_pool_events' ;
36
- import { PoolClosedError , WaitQueueTimeoutError } from './errors' ;
38
+ import { PoolClearedError , PoolClosedError , WaitQueueTimeoutError } from './errors' ;
37
39
import { ConnectionPoolMetrics } from './metrics' ;
38
40
39
41
/** @internal */
@@ -103,6 +105,7 @@ export interface CloseOptions {
103
105
/** @public */
104
106
export type ConnectionPoolEvents = {
105
107
connectionPoolCreated ( event : ConnectionPoolCreatedEvent ) : void ;
108
+ connectionPoolReady ( event : ConnectionPoolReadyEvent ) : void ;
106
109
connectionPoolClosed ( event : ConnectionPoolClosedEvent ) : void ;
107
110
connectionPoolCleared ( event : ConnectionPoolClearedEvent ) : void ;
108
111
connectionCreated ( event : ConnectionCreatedEvent ) : void ;
@@ -167,6 +170,11 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
167
170
* @event
168
171
*/
169
172
static readonly CONNECTION_POOL_CLEARED = CONNECTION_POOL_CLEARED ;
173
+ /**
174
+ * Emitted each time the connection pool is marked ready
175
+ * @event
176
+ */
177
+ static readonly CONNECTION_POOL_READY = CONNECTION_POOL_READY ;
170
178
/**
171
179
* Emitted when a connection is created.
172
180
* @event
@@ -242,7 +250,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
242
250
243
251
process . nextTick ( ( ) => {
244
252
this . emit ( ConnectionPool . CONNECTION_POOL_CREATED , new ConnectionPoolCreatedEvent ( this ) ) ;
245
- this . ensureMinPoolSize ( ) ;
246
253
} ) ;
247
254
}
248
255
@@ -308,7 +315,13 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
308
315
* Set the pool state to "ready"
309
316
*/
310
317
ready ( ) : void {
318
+ if ( this [ kPoolState ] !== PoolState . paused ) {
319
+ return ;
320
+ }
311
321
this [ kPoolState ] = PoolState . ready ;
322
+ this . emit ( ConnectionPool . CONNECTION_POOL_READY , new ConnectionPoolReadyEvent ( this ) ) ;
323
+ clearTimeout ( this [ kMinPoolSizeTimer ] ) ;
324
+ this . ensureMinPoolSize ( ) ;
312
325
}
313
326
314
327
/**
@@ -322,15 +335,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
322
335
new ConnectionCheckOutStartedEvent ( this )
323
336
) ;
324
337
325
- if ( this . closed ) {
326
- this . emit (
327
- ConnectionPool . CONNECTION_CHECK_OUT_FAILED ,
328
- new ConnectionCheckOutFailedEvent ( this , 'poolClosed' )
329
- ) ;
330
- callback ( new PoolClosedError ( this ) ) ;
331
- return ;
332
- }
333
-
334
338
const waitQueueMember : WaitQueueMember = { callback } ;
335
339
const waitQueueTimeoutMS = this . options . waitQueueTimeoutMS ;
336
340
if ( waitQueueTimeoutMS ) {
@@ -390,26 +394,40 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
390
394
* previous generation will eventually be pruned during subsequent checkouts.
391
395
*/
392
396
clear ( serviceId ?: ObjectId ) : void {
397
+ if ( this . closed ) {
398
+ return ;
399
+ }
400
+
401
+ // handle load balanced case
393
402
if ( this . loadBalanced && serviceId ) {
394
403
const sid = serviceId . toHexString ( ) ;
395
404
const generation = this . serviceGenerations . get ( sid ) ;
396
405
// Only need to worry if the generation exists, since it should
397
406
// always be there but typescript needs the check.
398
407
if ( generation == null ) {
399
- // TODO(NODE-3483)
400
408
throw new MongoRuntimeError ( 'Service generations are required in load balancer mode.' ) ;
401
409
} else {
402
410
// Increment the generation for the service id.
403
411
this . serviceGenerations . set ( sid , generation + 1 ) ;
404
412
}
405
- } else {
406
- this [ kGeneration ] += 1 ;
413
+ this . emit (
414
+ ConnectionPool . CONNECTION_POOL_CLEARED ,
415
+ new ConnectionPoolClearedEvent ( this , serviceId )
416
+ ) ;
417
+ return ;
407
418
}
408
419
409
- this . emit (
410
- ConnectionPool . CONNECTION_POOL_CLEARED ,
411
- new ConnectionPoolClearedEvent ( this , serviceId )
412
- ) ;
420
+ // handle non load-balanced case
421
+ this [ kGeneration ] += 1 ;
422
+ const alreadyPaused = this [ kPoolState ] === PoolState . paused ;
423
+ this [ kPoolState ] = PoolState . paused ;
424
+
425
+ this . clearMinPoolSizeTimer ( ) ;
426
+ this . processWaitQueue ( ) ;
427
+
428
+ if ( ! alreadyPaused ) {
429
+ this . emit ( ConnectionPool . CONNECTION_POOL_CLEARED , new ConnectionPoolClearedEvent ( this ) ) ;
430
+ }
413
431
}
414
432
415
433
/** Close the pool */
@@ -430,33 +448,15 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
430
448
// immediately cancel any in-flight connections
431
449
this [ kCancellationToken ] . emit ( 'cancel' ) ;
432
450
433
- // drain the wait queue
434
- while ( this . waitQueueSize ) {
435
- const waitQueueMember = this [ kWaitQueue ] . pop ( ) ;
436
- if ( waitQueueMember ) {
437
- if ( waitQueueMember . timer ) {
438
- clearTimeout ( waitQueueMember . timer ) ;
439
- }
440
- if ( ! waitQueueMember [ kCancelled ] ) {
441
- // TODO(NODE-3483): Replace with MongoConnectionPoolClosedError
442
- waitQueueMember . callback ( new MongoRuntimeError ( 'Connection pool closed' ) ) ;
443
- }
444
- }
445
- }
446
-
447
- // clear the min pool size timer
448
- const minPoolSizeTimer = this [ kMinPoolSizeTimer ] ;
449
- if ( minPoolSizeTimer ) {
450
- clearTimeout ( minPoolSizeTimer ) ;
451
- }
452
-
453
451
// end the connection counter
454
452
if ( typeof this [ kConnectionCounter ] . return === 'function' ) {
455
453
this [ kConnectionCounter ] . return ( undefined ) ;
456
454
}
457
455
458
- // mark the pool as closed immediately
459
456
this [ kPoolState ] = PoolState . closed ;
457
+ this . clearMinPoolSizeTimer ( ) ;
458
+ this . processWaitQueue ( ) ;
459
+
460
460
eachAsync < Connection > (
461
461
this [ kConnections ] . toArray ( ) ,
462
462
( conn , cb ) => {
@@ -526,12 +526,19 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
526
526
} ) ;
527
527
}
528
528
529
+ /** Clear the min pool size timer */
530
+ private clearMinPoolSizeTimer ( ) : void {
531
+ const minPoolSizeTimer = this [ kMinPoolSizeTimer ] ;
532
+ if ( minPoolSizeTimer ) {
533
+ clearTimeout ( minPoolSizeTimer ) ;
534
+ }
535
+ }
536
+
529
537
private destroyConnection ( connection : Connection , reason : string ) {
530
538
this . emit (
531
539
ConnectionPool . CONNECTION_CLOSED ,
532
540
new ConnectionClosedEvent ( this , connection , reason )
533
541
) ;
534
-
535
542
// destroy the connection
536
543
process . nextTick ( ( ) => connection . destroy ( ) ) ;
537
544
}
@@ -580,14 +587,16 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
580
587
connect ( connectOptions , ( err , connection ) => {
581
588
if ( err || ! connection ) {
582
589
this [ kLogger ] . debug ( `connection attempt failed with error [${ JSON . stringify ( err ) } ]` ) ;
583
- callback ( err ) ;
590
+ this [ kPending ] -- ;
591
+ callback ( err ?? new MongoRuntimeError ( 'Connection creation failed without error' ) ) ;
584
592
return ;
585
593
}
586
594
587
595
// The pool might have closed since we started trying to create a connection
588
- if ( this . closed ) {
596
+ if ( this [ kPoolState ] !== PoolState . ready ) {
589
597
this [ kPending ] -- ;
590
598
connection . destroy ( { force : true } ) ;
599
+ callback ( this . closed ? new PoolClosedError ( this ) : new PoolClearedError ( this ) ) ;
591
600
return ;
592
601
}
593
602
@@ -616,17 +625,25 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
616
625
connection . markAvailable ( ) ;
617
626
this . emit ( ConnectionPool . CONNECTION_READY , new ConnectionReadyEvent ( this , connection ) ) ;
618
627
628
+ this [ kPending ] -- ;
619
629
callback ( undefined , connection ) ;
620
630
return ;
621
631
} ) ;
622
632
}
623
633
624
634
private ensureMinPoolSize ( ) {
625
635
const minPoolSize = this . options . minPoolSize ;
626
- if ( this . closed || minPoolSize === 0 ) {
636
+ if ( this [ kPoolState ] !== PoolState . ready || minPoolSize === 0 ) {
627
637
return ;
628
638
}
629
639
640
+ for ( let i = 0 ; i < this [ kConnections ] . length ; i ++ ) {
641
+ const connection = this [ kConnections ] . peekAt ( i ) ;
642
+ if ( connection && this . connectionIsPerished ( connection ) ) {
643
+ this [ kConnections ] . removeOne ( i ) ;
644
+ }
645
+ }
646
+
630
647
if (
631
648
this . totalConnectionCount < minPoolSize &&
632
649
this . pendingConnectionCount < this . options . maxConnecting
@@ -635,23 +652,25 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
635
652
// connection permits because that potentially delays the availability of
636
653
// the connection to a checkout request
637
654
this . createConnection ( ( err , connection ) => {
638
- this [ kPending ] -- ;
639
655
if ( ! err && connection ) {
640
656
this [ kConnections ] . push ( connection ) ;
641
657
process . nextTick ( ( ) => this . processWaitQueue ( ) ) ;
642
658
}
643
- this [ kMinPoolSizeTimer ] = setTimeout ( ( ) => this . ensureMinPoolSize ( ) , 10 ) ;
659
+ if ( this [ kPoolState ] === PoolState . ready ) {
660
+ clearTimeout ( this [ kMinPoolSizeTimer ] ) ;
661
+ this [ kMinPoolSizeTimer ] = setTimeout ( ( ) => this . ensureMinPoolSize ( ) , 10 ) ;
662
+ }
644
663
} ) ;
645
664
} else {
665
+ clearTimeout ( this [ kMinPoolSizeTimer ] ) ;
646
666
this [ kMinPoolSizeTimer ] = setTimeout ( ( ) => this . ensureMinPoolSize ( ) , 100 ) ;
647
667
}
648
668
}
649
669
650
670
private processWaitQueue ( ) {
651
- if ( this . closed || this [ kProcessingWaitQueue ] ) {
671
+ if ( this [ kProcessingWaitQueue ] ) {
652
672
return ;
653
673
}
654
-
655
674
this [ kProcessingWaitQueue ] = true ;
656
675
657
676
while ( this . waitQueueSize ) {
@@ -666,6 +685,21 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
666
685
continue ;
667
686
}
668
687
688
+ if ( this [ kPoolState ] !== PoolState . ready ) {
689
+ const reason = this . closed ? 'poolClosed' : 'connectionError' ;
690
+ const error = this . closed ? new PoolClosedError ( this ) : new PoolClearedError ( this ) ;
691
+ this . emit (
692
+ ConnectionPool . CONNECTION_CHECK_OUT_FAILED ,
693
+ new ConnectionCheckOutFailedEvent ( this , reason )
694
+ ) ;
695
+ if ( waitQueueMember . timer ) {
696
+ clearTimeout ( waitQueueMember . timer ) ;
697
+ }
698
+ this [ kWaitQueue ] . shift ( ) ;
699
+ waitQueueMember . callback ( error ) ;
700
+ continue ;
701
+ }
702
+
669
703
if ( ! this . availableConnectionCount ) {
670
704
break ;
671
705
}
@@ -701,7 +735,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
701
735
continue ;
702
736
}
703
737
this . createConnection ( ( err , connection ) => {
704
- this [ kPending ] -- ;
705
738
if ( waitQueueMember [ kCancelled ] ) {
706
739
if ( ! err && connection ) {
707
740
this [ kConnections ] . push ( connection ) ;
@@ -710,7 +743,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
710
743
if ( err ) {
711
744
this . emit (
712
745
ConnectionPool . CONNECTION_CHECK_OUT_FAILED ,
713
- new ConnectionCheckOutFailedEvent ( this , err )
746
+ new ConnectionCheckOutFailedEvent ( this , 'connectionError' )
714
747
) ;
715
748
} else if ( connection ) {
716
749
this [ kCheckedOut ] ++ ;
0 commit comments