@@ -105,7 +105,8 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
105
105
rttPinger ?: RTTPinger ;
106
106
/** @internal */
107
107
override component = MongoLoggableComponent . TOPOLOGY ;
108
- private rttSamplesMS : MovingWindow ;
108
+ /** @internal */
109
+ rttSamplesMS : MovingWindow ;
109
110
110
111
constructor ( server : Server , options : MonitorOptions ) {
111
112
super ( ) ;
@@ -244,6 +245,8 @@ function resetMonitorState(monitor: Monitor) {
244
245
245
246
monitor . connection ?. destroy ( ) ;
246
247
monitor . connection = null ;
248
+
249
+ monitor . clearRttSamples ( ) ;
247
250
}
248
251
249
252
function useStreamingProtocol ( monitor : Monitor , topologyVersion : TopologyVersion | null ) : boolean {
@@ -277,7 +280,6 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
277
280
function onHeartbeatFailed ( err : Error ) {
278
281
monitor . connection ?. destroy ( ) ;
279
282
monitor . connection = null ;
280
-
281
283
monitor . emitAndLogHeartbeat (
282
284
Server . SERVER_HEARTBEAT_FAILED ,
283
285
monitor [ kServer ] . topology . s . id ,
@@ -306,7 +308,11 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
306
308
// NOTE: here we use the latestRTT as this measurment corresponds with the value
307
309
// obtained for this successful heartbeat
308
310
const duration =
309
- isAwaitable && monitor . rttPinger ? monitor . rttPinger . latestRTT : calculateDurationInMs ( start ) ;
311
+ isAwaitable && monitor . rttPinger
312
+ ? monitor . rttPinger . latestRTT ?? calculateDurationInMs ( start )
313
+ : calculateDurationInMs ( start ) ;
314
+
315
+ monitor . addRttSample ( duration ) ;
310
316
311
317
monitor . emitAndLogHeartbeat (
312
318
Server . SERVER_HEARTBEAT_SUCCEEDED ,
@@ -406,6 +412,8 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
406
412
connection . destroy ( ) ;
407
413
return ;
408
414
}
415
+ const duration = calculateDurationInMs ( start ) ;
416
+ monitor . addRttSample ( duration ) ;
409
417
410
418
monitor . connection = connection ;
411
419
monitor . emitAndLogHeartbeat (
@@ -414,7 +422,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
414
422
connection . hello ?. connectionId ,
415
423
new ServerHeartbeatSucceededEvent (
416
424
monitor . address ,
417
- calculateDurationInMs ( start ) ,
425
+ duration ,
418
426
connection . hello ,
419
427
useStreamingProtocol ( monitor , connection . hello ?. topologyVersion )
420
428
)
@@ -430,6 +438,173 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
430
438
) ;
431
439
}
432
440
441
+ async function _checkServer ( monitor : Monitor ) : Promise < Document | null > {
442
+ let start : number ;
443
+ let awaited : boolean ;
444
+ const topologyVersion = monitor [ kServer ] . description . topologyVersion ;
445
+ const isAwaitable = useStreamingProtocol ( monitor , topologyVersion ) ;
446
+ monitor . emitAndLogHeartbeat (
447
+ Server . SERVER_HEARTBEAT_STARTED ,
448
+ monitor [ kServer ] . topology . s . id ,
449
+ undefined ,
450
+ new ServerHeartbeatStartedEvent ( monitor . address , isAwaitable )
451
+ ) ;
452
+
453
+ function onHeartbeatFailed ( err : Error ) {
454
+ monitor . connection ?. destroy ( ) ;
455
+ monitor . connection = null ;
456
+ monitor . emitAndLogHeartbeat (
457
+ Server . SERVER_HEARTBEAT_FAILED ,
458
+ monitor [ kServer ] . topology . s . id ,
459
+ undefined ,
460
+ new ServerHeartbeatFailedEvent ( monitor . address , calculateDurationInMs ( start ) , err , awaited )
461
+ ) ;
462
+
463
+ const error = ! ( err instanceof MongoError )
464
+ ? new MongoError ( MongoError . buildErrorMessage ( err ) , { cause : err } )
465
+ : err ;
466
+ error . addErrorLabel ( MongoErrorLabel . ResetPool ) ;
467
+ if ( error instanceof MongoNetworkTimeoutError ) {
468
+ error . addErrorLabel ( MongoErrorLabel . InterruptInUseConnections ) ;
469
+ }
470
+
471
+ monitor . emit ( 'resetServer' , error ) ;
472
+ }
473
+
474
+ function onHeartbeatSucceeded ( hello : Document ) {
475
+ if ( ! ( 'isWritablePrimary' in hello ) ) {
476
+ // Provide hello-style response document.
477
+ hello . isWritablePrimary = hello [ LEGACY_HELLO_COMMAND ] ;
478
+ }
479
+
480
+ // NOTE: here we use the latestRTT as this measurment corresponds with the value
481
+ // obtained for this successful heartbeat
482
+ const duration =
483
+ isAwaitable && monitor . rttPinger
484
+ ? monitor . rttPinger . latestRTT ?? calculateDurationInMs ( start )
485
+ : calculateDurationInMs ( start ) ;
486
+
487
+ monitor . addRttSample ( duration ) ;
488
+
489
+ monitor . emitAndLogHeartbeat (
490
+ Server . SERVER_HEARTBEAT_SUCCEEDED ,
491
+ monitor [ kServer ] . topology . s . id ,
492
+ hello . connectionId ,
493
+ new ServerHeartbeatSucceededEvent ( monitor . address , duration , hello , isAwaitable )
494
+ ) ;
495
+
496
+ if ( isAwaitable ) {
497
+ // If we are using the streaming protocol then we immediately issue another 'started'
498
+ // event, otherwise the "check" is complete and return to the main monitor loop
499
+ monitor . emitAndLogHeartbeat (
500
+ Server . SERVER_HEARTBEAT_STARTED ,
501
+ monitor [ kServer ] . topology . s . id ,
502
+ undefined ,
503
+ new ServerHeartbeatStartedEvent ( monitor . address , true )
504
+ ) ;
505
+ // We have not actually sent an outgoing handshake, but when we get the next response we
506
+ // want the duration to reflect the time since we last heard from the server
507
+ start = now ( ) ;
508
+ } else {
509
+ monitor . rttPinger ?. close ( ) ;
510
+ monitor . rttPinger = undefined ;
511
+ }
512
+ }
513
+
514
+ const { connection } = monitor ;
515
+ if ( connection && ! connection . closed ) {
516
+ const { serverApi, helloOk } = connection ;
517
+ const connectTimeoutMS = monitor . options . connectTimeoutMS ;
518
+ const maxAwaitTimeMS = monitor . options . heartbeatFrequencyMS ;
519
+
520
+ const cmd = {
521
+ [ serverApi ?. version || helloOk ? 'hello' : LEGACY_HELLO_COMMAND ] : 1 ,
522
+ ...( isAwaitable && topologyVersion
523
+ ? { maxAwaitTimeMS, topologyVersion : makeTopologyVersion ( topologyVersion ) }
524
+ : { } )
525
+ } ;
526
+
527
+ const options = isAwaitable
528
+ ? {
529
+ socketTimeoutMS : connectTimeoutMS ? connectTimeoutMS + maxAwaitTimeMS : 0 ,
530
+ exhaustAllowed : true
531
+ }
532
+ : { socketTimeoutMS : connectTimeoutMS } ;
533
+
534
+ if ( isAwaitable && monitor . rttPinger == null ) {
535
+ monitor . rttPinger = new RTTPinger (
536
+ monitor ,
537
+ monitor [ kCancellationToken ] ,
538
+ Object . assign (
539
+ { heartbeatFrequencyMS : monitor . options . heartbeatFrequencyMS } ,
540
+ monitor . connectOptions
541
+ )
542
+ ) ;
543
+ }
544
+
545
+ // Record new start time before sending handshake
546
+ start = now ( ) ;
547
+
548
+ if ( isAwaitable ) {
549
+ awaited = true ;
550
+ try {
551
+ const hello = await connection . command ( ns ( 'admin.$cmd' ) , cmd , options ) ;
552
+ onHeartbeatSucceeded ( hello ) ;
553
+ return hello ;
554
+ } catch ( error ) {
555
+ onHeartbeatFailed ( error ) ;
556
+ return null ;
557
+ }
558
+ }
559
+
560
+ awaited = false ;
561
+ try {
562
+ const hello = await connection . command ( ns ( 'admin.$cmd' ) , cmd , options ) ;
563
+ onHeartbeatSucceeded ( hello ) ;
564
+ return hello ;
565
+ } catch ( error ) {
566
+ onHeartbeatFailed ( error ) ;
567
+ return null ;
568
+ }
569
+ } else {
570
+ const socket = await makeSocket ( monitor . connectOptions ) ;
571
+ const connection = makeConnection ( monitor . connectOptions , socket ) ;
572
+ // The start time is after socket creation but before the handshake
573
+ start = now ( ) ;
574
+ try {
575
+ await performInitialHandshake ( connection , monitor . connectOptions ) ;
576
+ const duration = calculateDurationInMs ( start ) ;
577
+ if ( isInCloseState ( monitor ) ) {
578
+ connection . destroy ( ) ;
579
+ return null ;
580
+ }
581
+
582
+ monitor . connection = connection ;
583
+ monitor . addRttSample ( duration ) ;
584
+
585
+ monitor . emitAndLogHeartbeat (
586
+ Server . SERVER_HEARTBEAT_SUCCEEDED ,
587
+ monitor [ kServer ] . topology . s . id ,
588
+ connection . hello ?. connectionId ,
589
+ new ServerHeartbeatSucceededEvent (
590
+ monitor . address ,
591
+ duration ,
592
+ connection . hello ,
593
+ useStreamingProtocol ( monitor , connection . hello ?. topologyVersion )
594
+ )
595
+ ) ;
596
+
597
+ return connection . hello ;
598
+ } catch ( error ) {
599
+ connection . destroy ( ) ;
600
+ monitor . connection = null ;
601
+ awaited = false ;
602
+ onHeartbeatFailed ( error ) ;
603
+ return null ;
604
+ }
605
+ }
606
+ }
607
+
433
608
function monitorServer ( monitor : Monitor ) {
434
609
return ( callback : Callback ) => {
435
610
if ( monitor . s . state === STATE_MONITORING ) {
@@ -491,12 +666,15 @@ export class RTTPinger {
491
666
/** @internal */
492
667
monitor : Monitor ;
493
668
closed : boolean ;
669
+ /** @internal */
670
+ latestRTT ?: number ;
494
671
495
672
constructor ( monitor : Monitor , cancellationToken : CancellationToken , options : RTTPingerOptions ) {
496
673
this . connection = undefined ;
497
674
this [ kCancellationToken ] = cancellationToken ;
498
675
this . closed = false ;
499
676
this . monitor = monitor ;
677
+ this . latestRTT = 0 ;
500
678
501
679
const heartbeatFrequencyMS = options . heartbeatFrequencyMS ;
502
680
this [ kMonitorId ] = setTimeout ( ( ) => measureRoundTripTime ( this , options ) , heartbeatFrequencyMS ) ;
@@ -510,10 +688,6 @@ export class RTTPinger {
510
688
return this . monitor . minRoundTripTime ;
511
689
}
512
690
513
- get latestRTT ( ) : number {
514
- return this . monitor . latestRTT ;
515
- }
516
-
517
691
close ( ) : void {
518
692
this . closed = true ;
519
693
clearTimeout ( this [ kMonitorId ] ) ;
@@ -523,42 +697,48 @@ export class RTTPinger {
523
697
}
524
698
}
525
699
526
- function measureRoundTripTime ( rttPinger : RTTPinger , options : RTTPingerOptions ) {
527
- const start = now ( ) ;
528
- options . cancellationToken = rttPinger [ kCancellationToken ] ;
529
- const heartbeatFrequencyMS = options . heartbeatFrequencyMS ;
530
-
700
+ function measureAndReschedule (
701
+ rttPinger : RTTPinger ,
702
+ options : RTTPingerOptions ,
703
+ start ?: number ,
704
+ conn ?: Connection
705
+ ) {
706
+ if ( start == null ) {
707
+ start = now ( ) ;
708
+ }
531
709
if ( rttPinger . closed ) {
710
+ conn ?. destroy ( ) ;
532
711
return ;
533
712
}
534
713
535
- function measureAndReschedule ( conn ?: Connection ) {
536
- if ( rttPinger . closed ) {
537
- conn ?. destroy ( ) ;
538
- return ;
539
- }
714
+ if ( rttPinger . connection == null ) {
715
+ rttPinger . connection = conn ;
716
+ }
540
717
541
- if ( rttPinger . connection == null ) {
542
- rttPinger . connection = conn ;
543
- }
718
+ rttPinger . latestRTT = calculateDurationInMs ( start ) ;
719
+ rttPinger [ kMonitorId ] = setTimeout (
720
+ ( ) => measureRoundTripTime ( rttPinger , options ) ,
721
+ options . heartbeatFrequencyMS
722
+ ) ;
723
+ }
544
724
545
- rttPinger . monitor . addRttSample ( calculateDurationInMs ( start ) ) ;
546
- rttPinger [ kMonitorId ] = setTimeout (
547
- ( ) => measureRoundTripTime ( rttPinger , options ) ,
548
- heartbeatFrequencyMS
549
- ) ;
725
+ function measureRoundTripTime ( rttPinger : RTTPinger , options : RTTPingerOptions ) {
726
+ const start = now ( ) ;
727
+ options . cancellationToken = rttPinger [ kCancellationToken ] ;
728
+
729
+ if ( rttPinger . closed ) {
730
+ return ;
550
731
}
551
732
552
733
const connection = rttPinger . connection ;
553
734
if ( connection == null ) {
554
735
// eslint-disable-next-line github/no-then
555
736
connect ( options ) . then (
556
737
connection => {
557
- measureAndReschedule ( connection ) ;
738
+ measureAndReschedule ( rttPinger , options , start , connection ) ;
558
739
} ,
559
740
( ) => {
560
741
rttPinger . connection = undefined ;
561
- rttPinger . monitor . clearRttSamples ( ) ;
562
742
}
563
743
) ;
564
744
return ;
@@ -568,11 +748,10 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) {
568
748
connection . serverApi ?. version || connection . helloOk ? 'hello' : LEGACY_HELLO_COMMAND ;
569
749
// eslint-disable-next-line github/no-then
570
750
connection . command ( ns ( 'admin.$cmd' ) , { [ commandName ] : 1 } , undefined ) . then (
571
- ( ) => measureAndReschedule ( ) ,
751
+ ( ) => measureAndReschedule ( rttPinger , options ) ,
572
752
( ) => {
573
753
rttPinger . connection ?. destroy ( ) ;
574
754
rttPinger . connection = undefined ;
575
- rttPinger . monitor . clearRttSamples ( ) ;
576
755
return ;
577
756
}
578
757
) ;
0 commit comments