@@ -19,6 +19,7 @@ import {
1919 MongoMissingDependencyError ,
2020 MongoNetworkError ,
2121 MongoNetworkTimeoutError ,
22+ MongoOperationTimeoutError ,
2223 MongoParseError ,
2324 MongoServerError ,
2425 MongoUnexpectedServerResponseError
@@ -30,7 +31,7 @@ import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
3031import { ReadPreference , type ReadPreferenceLike } from '../read_preference' ;
3132import { ServerType } from '../sdam/common' ;
3233import { applySession , type ClientSession , updateSessionFromResponse } from '../sessions' ;
33- import { type TimeoutContext } from '../timeout' ;
34+ import { type TimeoutContext , TimeoutError } from '../timeout' ;
3435import {
3536 BufferPool ,
3637 calculateDurationInMs ,
@@ -419,6 +420,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
419420 ...options
420421 } ;
421422
423+ if ( options . timeoutContext ?. csotEnabled ( ) ) {
424+ const { maxTimeMS } = options . timeoutContext ;
425+ if ( maxTimeMS > 0 && Number . isFinite ( maxTimeMS ) ) cmd . maxTimeMS = maxTimeMS ;
426+ }
427+
422428 const message = this . supportsOpMsg
423429 ? new OpMsgRequest ( db , cmd , commandOptions )
424430 : new OpQueryRequest ( db , cmd , commandOptions ) ;
@@ -433,7 +439,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
433439 ) : AsyncGenerator < MongoDBResponse > {
434440 this . throwIfAborted ( ) ;
435441
436- if ( typeof options . socketTimeoutMS === 'number' ) {
442+ if ( options . timeoutContext ?. csotEnabled ( ) ) {
443+ this . socket . setTimeout ( 0 ) ;
444+ } else if ( typeof options . socketTimeoutMS === 'number' ) {
437445 this . socket . setTimeout ( options . socketTimeoutMS ) ;
438446 } else if ( this . socketTimeoutMS !== 0 ) {
439447 this . socket . setTimeout ( this . socketTimeoutMS ) ;
@@ -442,7 +450,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
442450 try {
443451 await this . writeCommand ( message , {
444452 agreedCompressor : this . description . compressor ?? 'none' ,
445- zlibCompressionLevel : this . description . zlibCompressionLevel
453+ zlibCompressionLevel : this . description . zlibCompressionLevel ,
454+ timeoutContext : options . timeoutContext
446455 } ) ;
447456
448457 if ( options . noResponse || message . moreToCome ) {
@@ -452,7 +461,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
452461
453462 this . throwIfAborted ( ) ;
454463
455- for await ( const response of this . readMany ( ) ) {
464+ if (
465+ options . timeoutContext ?. csotEnabled ( ) &&
466+ options . timeoutContext . minRoundTripTime != null &&
467+ options . timeoutContext . remainingTimeMS < options . timeoutContext . minRoundTripTime
468+ ) {
469+ throw new MongoOperationTimeoutError (
470+ 'Server roundtrip time is greater than the time remaining'
471+ ) ;
472+ }
473+
474+ for await ( const response of this . readMany ( { timeoutContext : options . timeoutContext } ) ) {
456475 this . socket . setTimeout ( 0 ) ;
457476 const bson = response . parse ( ) ;
458477
@@ -629,7 +648,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
629648 */
630649 private async writeCommand (
631650 command : WriteProtocolMessageType ,
632- options : { agreedCompressor ?: CompressorName ; zlibCompressionLevel ?: number }
651+ options : {
652+ agreedCompressor ?: CompressorName ;
653+ zlibCompressionLevel ?: number ;
654+ timeoutContext ?: TimeoutContext ;
655+ }
633656 ) : Promise < void > {
634657 const finalCommand =
635658 options . agreedCompressor === 'none' || ! OpCompressedRequest . canCompress ( command )
@@ -641,8 +664,32 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
641664
642665 const buffer = Buffer . concat ( await finalCommand . toBin ( ) ) ;
643666
667+ if ( options . timeoutContext ?. csotEnabled ( ) ) {
668+ if (
669+ options . timeoutContext . minRoundTripTime != null &&
670+ options . timeoutContext . remainingTimeMS < options . timeoutContext . minRoundTripTime
671+ ) {
672+ throw new MongoOperationTimeoutError (
673+ 'Server roundtrip time is greater than the time remaining'
674+ ) ;
675+ }
676+ }
677+
644678 if ( this . socket . write ( buffer ) ) return ;
645- return await once ( this . socket , 'drain' ) ;
679+
680+ const drainEvent = once < void > ( this . socket , 'drain' ) ;
681+ const timeout = options ?. timeoutContext ?. timeoutForSocketWrite ;
682+ if ( timeout ) {
683+ try {
684+ return await Promise . race ( [ drainEvent , timeout ] ) ;
685+ } catch ( error ) {
686+ if ( TimeoutError . is ( error ) ) {
687+ throw new MongoOperationTimeoutError ( 'Timed out at socket write' ) ;
688+ }
689+ throw error ;
690+ }
691+ }
692+ return await drainEvent ;
646693 }
647694
648695 /**
@@ -654,10 +701,13 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
654701 *
655702 * Note that `for-await` loops call `return` automatically when the loop is exited.
656703 */
657- private async * readMany ( ) : AsyncGenerator < OpMsgResponse | OpReply > {
704+ private async * readMany ( options : {
705+ timeoutContext ?: TimeoutContext ;
706+ } ) : AsyncGenerator < OpMsgResponse | OpReply > {
658707 try {
659- this . dataEvents = onData ( this . messageStream ) ;
708+ this . dataEvents = onData ( this . messageStream , options ) ;
660709 this . messageStream . resume ( ) ;
710+
661711 for await ( const message of this . dataEvents ) {
662712 const response = await decompressResponse ( message ) ;
663713 yield response ;
0 commit comments