@@ -30,7 +30,7 @@ import { ReadConcernLevel } from './read_concern';
30
30
import { ReadPreference } from './read_preference' ;
31
31
import { type AsyncDisposable , configureResourceManagement } from './resource_management' ;
32
32
import { _advanceClusterTime , type ClusterTime , TopologyType } from './sdam/common' ;
33
- import { type TimeoutContext } from './timeout' ;
33
+ import { TimeoutContext } from './timeout' ;
34
34
import {
35
35
isTransactionCommand ,
36
36
Transaction ,
@@ -280,12 +280,13 @@ export class ClientSession
280
280
async endSession ( options ?: EndSessionOptions ) : Promise < void > {
281
281
try {
282
282
if ( this . inTransaction ( ) ) {
283
- if ( typeof options ?. timeoutMS === 'number' ) {
284
- await endTransaction ( this , 'abortTransaction' , { timeoutMS : options . timeoutMS } ) ;
285
- } else {
286
- await endTransaction ( this , 'abortTransaction' ) ;
287
- }
283
+ await this . abortTransaction ( { ...options , throwTimeout : true } ) ;
288
284
}
285
+ } catch ( error ) {
286
+ // spec indicates that we should ignore all errors for `endSessions`
287
+ if ( MongoOperationTimeoutError . is ( error ) ) throw error ;
288
+ squashError ( error ) ;
289
+ } finally {
289
290
if ( ! this . hasEnded ) {
290
291
const serverSession = this [ kServerSession ] ;
291
292
if ( serverSession != null ) {
@@ -301,11 +302,6 @@ export class ClientSession
301
302
this . hasEnded = true ;
302
303
this . emit ( 'ended' , this ) ;
303
304
}
304
- } catch ( error ) {
305
- // spec indicates that we should ignore all errors for `endSessions`
306
- if ( MongoOperationTimeoutError . is ( error ) ) throw error ;
307
- squashError ( error ) ;
308
- } finally {
309
305
maybeClearPinnedConnection ( this , { force : true , ...options } ) ;
310
306
}
311
307
}
@@ -460,7 +456,7 @@ export class ClientSession
460
456
*
461
457
* @param options - Optional options, can be used to override `defaultTimeoutMS`.
462
458
*/
463
- async commitTransaction ( ) : Promise < void > {
459
+ async commitTransaction ( options ?: { timeoutMS ?: number } ) : Promise < void > {
464
460
if ( this . transaction . state === TxnState . NO_TRANSACTION ) {
465
461
throw new MongoTransactionError ( 'No transaction started' ) ;
466
462
}
@@ -510,8 +506,19 @@ export class ClientSession
510
506
bypassPinningCheck : true
511
507
} ) ;
512
508
509
+ const timeoutMS =
510
+ typeof options ?. timeoutMS === 'number'
511
+ ? options . timeoutMS
512
+ : typeof this . timeoutMS === 'number'
513
+ ? this . timeoutMS
514
+ : null ;
515
+
516
+ const timeoutContext = this . timeoutContext ?. csotEnabled ( )
517
+ ? this . timeoutContext
518
+ : TimeoutContext . create ( { timeoutMS, ...this . clientOptions } ) ;
519
+
513
520
try {
514
- await executeOperation ( this . client , operation ) ;
521
+ await executeOperation ( this . client , operation , timeoutContext ) ;
515
522
return ;
516
523
} catch ( firstCommitError ) {
517
524
if ( firstCommitError instanceof MongoError && isRetryableWriteError ( firstCommitError ) ) {
@@ -521,7 +528,7 @@ export class ClientSession
521
528
this . unpin ( { force : true } ) ;
522
529
523
530
try {
524
- await executeOperation ( this . client , operation ) ;
531
+ await executeOperation ( this . client , operation , timeoutContext ) ;
525
532
return ;
526
533
} catch ( retryCommitError ) {
527
534
// If the retry failed, we process that error instead of the original
@@ -556,7 +563,10 @@ export class ClientSession
556
563
*
557
564
* @param options - Optional options, can be used to override `defaultTimeoutMS`.
558
565
*/
559
- async abortTransaction ( ) : Promise < void > {
566
+ async abortTransaction ( options ?: { timeoutMS ?: number } ) : Promise < void > ;
567
+ /** @internal */
568
+ async abortTransaction ( options ?: { timeoutMS ?: number ; throwTimeout ?: true } ) : Promise < void > ;
569
+ async abortTransaction ( options ?: { timeoutMS ?: number ; throwTimeout ?: true } ) : Promise < void > {
560
570
if ( this . transaction . state === TxnState . NO_TRANSACTION ) {
561
571
throw new MongoTransactionError ( 'No transaction started' ) ;
562
572
}
@@ -601,18 +611,34 @@ export class ClientSession
601
611
bypassPinningCheck : true
602
612
} ) ;
603
613
614
+ const timeoutMS =
615
+ typeof options ?. timeoutMS === 'number'
616
+ ? options . timeoutMS
617
+ : this . timeoutContext ?. csotEnabled ( )
618
+ ? this . timeoutContext . timeoutMS // refresh timeoutMS for abort operation
619
+ : typeof this . timeoutMS === 'number'
620
+ ? this . timeoutMS
621
+ : null ;
622
+
623
+ const timeoutContext = TimeoutContext . create ( { timeoutMS, ...this . clientOptions } ) ;
624
+
604
625
try {
605
- await executeOperation ( this . client , operation ) ;
626
+ await executeOperation ( this . client , operation , timeoutContext ) ;
606
627
this . unpin ( ) ;
607
628
return ;
608
629
} catch ( firstAbortError ) {
609
630
this . unpin ( ) ;
610
631
632
+ if ( options ?. throwTimeout && MongoOperationTimeoutError . is ( firstAbortError ) )
633
+ throw firstAbortError ;
634
+
611
635
if ( firstAbortError instanceof MongoError && isRetryableWriteError ( firstAbortError ) ) {
612
636
try {
613
- await executeOperation ( this . client , operation ) ;
637
+ await executeOperation ( this . client , operation , timeoutContext ) ;
614
638
return ;
615
639
} catch ( secondAbortError ) {
640
+ if ( options ?. throwTimeout && MongoOperationTimeoutError . is ( secondAbortError ) )
641
+ throw secondAbortError ;
616
642
// we do not retry the retry
617
643
}
618
644
}
@@ -670,93 +696,102 @@ export class ClientSession
670
696
options ?: TransactionOptions
671
697
) : Promise < T > {
672
698
const MAX_TIMEOUT = 120000 ;
673
- const startTime = now ( ) ;
674
-
675
- let committed = false ;
676
- let result : any ;
677
699
678
- while ( ! committed ) {
679
- this . startTransaction ( options ) ; // may throw on error
700
+ this . timeoutContext =
701
+ options != null && 'timeoutMS' in options && typeof options . timeoutMS === 'number'
702
+ ? TimeoutContext . create ( { timeoutMS : options . timeoutMS , ...this . clientOptions } )
703
+ : null ;
680
704
681
- try {
682
- const promise = fn ( this ) ;
683
- if ( ! isPromiseLike ( promise ) ) {
684
- throw new MongoInvalidArgumentError (
685
- 'Function provided to `withTransaction` must return a Promise'
686
- ) ;
687
- }
705
+ const startTime = this . timeoutContext ?. csotEnabled ( ) ? this . timeoutContext . start : now ( ) ;
688
706
689
- result = await promise ;
707
+ let committed = false ;
708
+ let result : any ;
690
709
691
- if (
692
- this . transaction . state === TxnState . NO_TRANSACTION ||
693
- this . transaction . state === TxnState . TRANSACTION_COMMITTED ||
694
- this . transaction . state === TxnState . TRANSACTION_ABORTED
695
- ) {
696
- // Assume callback intentionally ended the transaction
697
- return result ;
698
- }
699
- } catch ( fnError ) {
700
- if ( ! ( fnError instanceof MongoError ) || fnError instanceof MongoInvalidArgumentError ) {
701
- await this . abortTransaction ( ) ;
702
- throw fnError ;
703
- }
710
+ try {
711
+ while ( ! committed ) {
712
+ this . startTransaction ( options ) ; // may throw on error
704
713
705
- if (
706
- this . transaction . state === TxnState . STARTING_TRANSACTION ||
707
- this . transaction . state === TxnState . TRANSACTION_IN_PROGRESS
708
- ) {
709
- await this . abortTransaction ( ) ;
710
- }
714
+ try {
715
+ const promise = fn ( this ) ;
716
+ if ( ! isPromiseLike ( promise ) ) {
717
+ throw new MongoInvalidArgumentError (
718
+ 'Function provided to `withTransaction` must return a Promise'
719
+ ) ;
720
+ }
711
721
712
- if (
713
- fnError . hasErrorLabel ( MongoErrorLabel . TransientTransactionError ) &&
714
- now ( ) - startTime < MAX_TIMEOUT
715
- ) {
716
- continue ;
717
- }
722
+ result = await promise ;
718
723
719
- throw fnError ;
720
- }
724
+ if (
725
+ this . transaction . state === TxnState . NO_TRANSACTION ||
726
+ this . transaction . state === TxnState . TRANSACTION_COMMITTED ||
727
+ this . transaction . state === TxnState . TRANSACTION_ABORTED
728
+ ) {
729
+ // Assume callback intentionally ended the transaction
730
+ return result ;
731
+ }
732
+ } catch ( fnError ) {
733
+ if ( ! ( fnError instanceof MongoError ) || fnError instanceof MongoInvalidArgumentError ) {
734
+ await this . abortTransaction ( ) ;
735
+ throw fnError ;
736
+ }
721
737
722
- while ( ! committed ) {
723
- try {
724
- /*
725
- * We will rely on ClientSession.commitTransaction() to
726
- * apply a majority write concern if commitTransaction is
727
- * being retried (see: DRIVERS-601)
728
- */
729
- await this . commitTransaction ( ) ;
730
- committed = true ;
731
- } catch ( commitError ) {
732
- /*
733
- * Note: a maxTimeMS error will have the MaxTimeMSExpired
734
- * code (50) and can be reported as a top-level error or
735
- * inside writeConcernError, ex.
736
- * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' }
737
- * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } }
738
- */
739
738
if (
740
- ! isMaxTimeMSExpiredError ( commitError ) &&
741
- commitError . hasErrorLabel ( MongoErrorLabel . UnknownTransactionCommitResult ) &&
742
- now ( ) - startTime < MAX_TIMEOUT
739
+ this . transaction . state === TxnState . STARTING_TRANSACTION ||
740
+ this . transaction . state === TxnState . TRANSACTION_IN_PROGRESS
743
741
) {
744
- continue ;
742
+ await this . abortTransaction ( ) ;
745
743
}
746
744
747
745
if (
748
- commitError . hasErrorLabel ( MongoErrorLabel . TransientTransactionError ) &&
749
- now ( ) - startTime < MAX_TIMEOUT
746
+ fnError . hasErrorLabel ( MongoErrorLabel . TransientTransactionError ) &&
747
+ ( this . timeoutContext != null || now ( ) - startTime < MAX_TIMEOUT )
750
748
) {
751
- break ;
749
+ continue ;
752
750
}
753
751
754
- throw commitError ;
752
+ throw fnError ;
753
+ }
754
+
755
+ while ( ! committed ) {
756
+ try {
757
+ /*
758
+ * We will rely on ClientSession.commitTransaction() to
759
+ * apply a majority write concern if commitTransaction is
760
+ * being retried (see: DRIVERS-601)
761
+ */
762
+ await this . commitTransaction ( ) ;
763
+ committed = true ;
764
+ } catch ( commitError ) {
765
+ /*
766
+ * Note: a maxTimeMS error will have the MaxTimeMSExpired
767
+ * code (50) and can be reported as a top-level error or
768
+ * inside writeConcernError, ex.
769
+ * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' }
770
+ * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } }
771
+ */
772
+ if (
773
+ ! isMaxTimeMSExpiredError ( commitError ) &&
774
+ commitError . hasErrorLabel ( MongoErrorLabel . UnknownTransactionCommitResult ) &&
775
+ ( this . timeoutContext != null || now ( ) - startTime < MAX_TIMEOUT )
776
+ ) {
777
+ continue ;
778
+ }
779
+
780
+ if (
781
+ commitError . hasErrorLabel ( MongoErrorLabel . TransientTransactionError ) &&
782
+ ( this . timeoutContext != null || now ( ) - startTime < MAX_TIMEOUT )
783
+ ) {
784
+ break ;
785
+ }
786
+
787
+ throw commitError ;
788
+ }
755
789
}
756
790
}
791
+ return result ;
792
+ } finally {
793
+ this . timeoutContext = null ;
757
794
}
758
-
759
- return result ;
760
795
}
761
796
}
762
797
0 commit comments