@@ -29,7 +29,7 @@ import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
2929import { ReadPreference , type ReadPreferenceLike } from '../read_preference' ;
3030import { ServerType } from '../sdam/common' ;
3131import { applySession , type ClientSession , updateSessionFromResponse } from '../sessions' ;
32- import { type TimeoutContext } from '../timeout' ;
32+ import { Timeout , type TimeoutContext , TimeoutError } from '../timeout' ;
3333import {
3434 BufferPool ,
3535 calculateDurationInMs ,
@@ -416,6 +416,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
416416 ...options
417417 } ;
418418
419+ if ( options . timeoutContext ?. csotEnabled ( ) ) {
420+ const { maxTimeMS } = options . timeoutContext ;
421+ if ( maxTimeMS > 0 && Number . isFinite ( maxTimeMS ) ) cmd . maxTimeMS = maxTimeMS ;
422+ }
423+
419424 const message = this . supportsOpMsg
420425 ? new OpMsgRequest ( db , cmd , commandOptions )
421426 : new OpQueryRequest ( db , cmd , commandOptions ) ;
@@ -430,7 +435,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
430435 ) : AsyncGenerator < MongoDBResponse > {
431436 this . throwIfAborted ( ) ;
432437
433- if ( typeof options . socketTimeoutMS === 'number' ) {
438+ if ( options . timeoutContext ?. csotEnabled ( ) ) {
439+ this . socket . setTimeout ( 0 ) ;
440+ } else if ( typeof options . socketTimeoutMS === 'number' ) {
434441 this . socket . setTimeout ( options . socketTimeoutMS ) ;
435442 } else if ( this . socketTimeoutMS !== 0 ) {
436443 this . socket . setTimeout ( this . socketTimeoutMS ) ;
@@ -439,7 +446,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
439446 try {
440447 await this . writeCommand ( message , {
441448 agreedCompressor : this . description . compressor ?? 'none' ,
442- zlibCompressionLevel : this . description . zlibCompressionLevel
449+ zlibCompressionLevel : this . description . zlibCompressionLevel ,
450+ timeoutContext : options . timeoutContext
443451 } ) ;
444452
445453 if ( options . noResponse ) {
@@ -449,7 +457,15 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
449457
450458 this . throwIfAborted ( ) ;
451459
452- for await ( const response of this . readMany ( ) ) {
460+ if (
461+ options . timeoutContext ?. csotEnabled ( ) &&
462+ options . timeoutContext . minRoundTripTime != null &&
463+ options . timeoutContext . remainingTimeMS < options . timeoutContext . minRoundTripTime
464+ ) {
465+ throw new TimeoutError ( 'Server roundtrip time is greater than the time remaining' ) ;
466+ }
467+
468+ for await ( const response of this . readMany ( { timeoutContext : options . timeoutContext } ) ) {
453469 this . socket . setTimeout ( 0 ) ;
454470 const bson = response . parse ( ) ;
455471
@@ -622,7 +638,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
622638 */
623639 private async writeCommand (
624640 command : WriteProtocolMessageType ,
625- options : { agreedCompressor ?: CompressorName ; zlibCompressionLevel ?: number }
641+ options : {
642+ agreedCompressor ?: CompressorName ;
643+ zlibCompressionLevel ?: number ;
644+ timeoutContext ?: TimeoutContext ;
645+ }
626646 ) : Promise < void > {
627647 const finalCommand =
628648 options . agreedCompressor === 'none' || ! OpCompressedRequest . canCompress ( command )
@@ -634,8 +654,23 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
634654
635655 const buffer = Buffer . concat ( await finalCommand . toBin ( ) ) ;
636656
657+ if ( options . timeoutContext ?. csotEnabled ( ) ) {
658+ if (
659+ options . timeoutContext . minRoundTripTime != null &&
660+ options . timeoutContext . remainingTimeMS < options . timeoutContext . minRoundTripTime
661+ ) {
662+ throw new TimeoutError ( 'Server roundtrip time is greater than the time remaining' ) ;
663+ }
664+ }
665+
637666 if ( this . socket . write ( buffer ) ) return ;
638- return await once ( this . socket , 'drain' ) ;
667+
668+ const drainEvent = once < void > ( this . socket , 'drain' ) ;
669+ if ( options . timeoutContext ?. csotEnabled ( ) ) {
670+ const timeout = Timeout . expires ( options . timeoutContext . remainingTimeMS ) ;
671+ return await Promise . race ( [ drainEvent , timeout ] ) ;
672+ }
673+ return await drainEvent ;
639674 }
640675
641676 /**
@@ -647,9 +682,16 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
647682 *
648683 * Note that `for-await` loops call `return` automatically when the loop is exited.
649684 */
650- private async * readMany ( ) : AsyncGenerator < OpMsgResponse | OpReply > {
685+ private async * readMany ( options : {
686+ timeoutContext ?: TimeoutContext ;
687+ } ) : AsyncGenerator < OpMsgResponse | OpReply > {
651688 try {
652- this . dataEvents = onData ( this . messageStream ) ;
689+ const timeoutMS = options . timeoutContext ?. csotEnabled ( )
690+ ? options . timeoutContext . remainingTimeMS
691+ : 0 ;
692+
693+ this . dataEvents = onData ( this . messageStream , { timeoutMS } ) ;
694+
653695 for await ( const message of this . dataEvents ) {
654696 const response = await decompressResponse ( message ) ;
655697 yield response ;
0 commit comments