@@ -41,7 +41,12 @@ import {
41
41
ConnectionPoolReadyEvent ,
42
42
ConnectionReadyEvent
43
43
} from './connection_pool_events' ;
44
- import { PoolClearedError , PoolClosedError , WaitQueueTimeoutError } from './errors' ;
44
+ import {
45
+ PoolClearedError ,
46
+ PoolClearedOnNetworkError ,
47
+ PoolClosedError ,
48
+ WaitQueueTimeoutError
49
+ } from './errors' ;
45
50
import { ConnectionPoolMetrics } from './metrics' ;
46
51
47
52
/** @internal */
@@ -391,10 +396,10 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
391
396
this [ kConnections ] . unshift ( connection ) ;
392
397
}
393
398
394
- this [ kCheckedOut ] . delete ( connection ) ;
399
+ const wasConnectionDeleted = this [ kCheckedOut ] . delete ( connection ) ;
395
400
this . emit ( ConnectionPool . CONNECTION_CHECKED_IN , new ConnectionCheckedInEvent ( this , connection ) ) ;
396
401
397
- if ( willDestroy ) {
402
+ if ( wasConnectionDeleted && willDestroy ) {
398
403
const reason = connection . closed ? 'error' : poolClosed ? 'poolClosed' : 'stale' ;
399
404
this . destroyConnection ( connection , reason ) ;
400
405
}
@@ -408,8 +413,9 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
408
413
* Pool reset is handled by incrementing the pool's generation count. Any existing connection of a
409
414
* previous generation will eventually be pruned during subsequent checkouts.
410
415
*/
411
- clear ( options : { serviceId ?: ObjectId } = { } ) : void {
416
+ clear ( options : { serviceId ?: ObjectId ; interruptInUseConnections ?: boolean } = { } ) : void {
412
417
const { serviceId } = options ;
418
+ const interruptInUseConnections = options . interruptInUseConnections ?? false ;
413
419
if ( this . closed ) {
414
420
return ;
415
421
}
@@ -433,18 +439,72 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
433
439
return ;
434
440
}
435
441
442
+ const oldGeneration = this [ kGeneration ] ;
443
+
436
444
// handle non load-balanced case
437
445
this [ kGeneration ] += 1 ;
438
446
const alreadyPaused = this [ kPoolState ] === PoolState . paused ;
439
447
this [ kPoolState ] = PoolState . paused ;
440
448
441
449
this . clearMinPoolSizeTimer ( ) ;
442
450
if ( ! alreadyPaused ) {
443
- this . emit ( ConnectionPool . CONNECTION_POOL_CLEARED , new ConnectionPoolClearedEvent ( this ) ) ;
451
+ this . emit (
452
+ ConnectionPool . CONNECTION_POOL_CLEARED ,
453
+ new ConnectionPoolClearedEvent ( this , { interruptInUseConnections } )
454
+ ) ;
444
455
}
456
+
457
+ process . nextTick ( ( ) =>
458
+ this . pruneConnections ( { minGeneration : oldGeneration , interruptInUseConnections } )
459
+ ) ;
460
+
445
461
this . processWaitQueue ( ) ;
446
462
}
447
463
464
+ /**
465
+ * Closes all checked in perished connections in the pool with a resumable PoolClearedOnNetworkError.
466
+ *
467
+ * If interruptInUseConnections is `true`, this method attempts to kill checked out connections as well.
468
+ * Only connections where `connection.generation <= minGeneration` are killed. Connections are closed with a
469
+ * resumable PoolClearedOnNetworkTimeoutError.
470
+ */
471
+ private pruneConnections ( {
472
+ interruptInUseConnections,
473
+ minGeneration
474
+ } : {
475
+ interruptInUseConnections : boolean ;
476
+ minGeneration : number ;
477
+ } ) {
478
+ this [ kConnections ] . prune ( connection => {
479
+ if ( connection . generation <= minGeneration ) {
480
+ connection . onError ( new PoolClearedOnNetworkError ( this ) ) ;
481
+ this . emit (
482
+ ConnectionPool . CONNECTION_CLOSED ,
483
+ new ConnectionClosedEvent ( this , connection , 'stale' )
484
+ ) ;
485
+
486
+ return true ;
487
+ }
488
+ return false ;
489
+ } ) ;
490
+
491
+ if ( interruptInUseConnections ) {
492
+ for ( const connection of this [ kCheckedOut ] ) {
493
+ if ( connection . generation <= minGeneration ) {
494
+ this [ kCheckedOut ] . delete ( connection ) ;
495
+ connection . onError ( new PoolClearedOnNetworkError ( this ) ) ;
496
+ this . emit (
497
+ ConnectionPool . CONNECTION_CLOSED ,
498
+ new ConnectionClosedEvent ( this , connection , 'stale' )
499
+ ) ;
500
+ }
501
+ }
502
+
503
+ // TODO(NODE-xxxx): track pending connections and cancel
504
+ // this[kCancellationToken].emit('cancel');
505
+ }
506
+ }
507
+
448
508
/** Close the pool */
449
509
close ( callback : Callback < void > ) : void ;
450
510
close ( options : CloseOptions , callback : Callback < void > ) : void ;
@@ -573,7 +633,12 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
573
633
return ! ! ( this . options . maxIdleTimeMS && connection . idleTime > this . options . maxIdleTimeMS ) ;
574
634
}
575
635
576
- private connectionIsPerished ( connection : Connection ) {
636
+ /**
637
+ * Destroys a connection if the connection is perished.
638
+ *
639
+ * @returns `true` if the connection was destroyed, `false` otherwise.
640
+ */
641
+ private destroyConnectionIfPerished ( connection : Connection ) {
577
642
const isStale = this . connectionIsStale ( connection ) ;
578
643
const isIdle = this . connectionIsIdle ( connection ) ;
579
644
if ( ! isStale && ! isIdle && ! connection . closed ) {
@@ -659,7 +724,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
659
724
return ;
660
725
}
661
726
662
- this [ kConnections ] . prune ( connection => this . connectionIsPerished ( connection ) ) ;
727
+ this [ kConnections ] . prune ( connection => this . destroyConnectionIfPerished ( connection ) ) ;
663
728
664
729
if (
665
730
this . totalConnectionCount < minPoolSize &&
@@ -735,7 +800,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
735
800
break ;
736
801
}
737
802
738
- if ( ! this . connectionIsPerished ( connection ) ) {
803
+ if ( ! this . destroyConnectionIfPerished ( connection ) ) {
739
804
this [ kCheckedOut ] . add ( connection ) ;
740
805
this . emit (
741
806
ConnectionPool . CONNECTION_CHECKED_OUT ,
0 commit comments