@@ -16,6 +16,7 @@ import {
16
16
MongoErrorLabel ,
17
17
MongoExpiredSessionError ,
18
18
MongoInvalidArgumentError ,
19
+ MongoOperationTimeoutError ,
19
20
MongoRuntimeError ,
20
21
MongoServerError ,
21
22
MongoTransactionError ,
@@ -29,6 +30,7 @@ import { ReadConcernLevel } from './read_concern';
29
30
import { ReadPreference } from './read_preference' ;
30
31
import { type AsyncDisposable , configureResourceManagement } from './resource_management' ;
31
32
import { _advanceClusterTime , type ClusterTime , TopologyType } from './sdam/common' ;
33
+ import { TimeoutContext } from './timeout' ;
32
34
import {
33
35
isTransactionCommand ,
34
36
Transaction ,
@@ -101,6 +103,9 @@ export interface EndSessionOptions {
101
103
error ?: AnyError ;
102
104
force ?: boolean ;
103
105
forceClear ?: boolean ;
106
+
107
+ /** @internal */
108
+ timeoutMS ?: number ;
104
109
}
105
110
106
111
/**
@@ -118,7 +123,7 @@ export class ClientSession
118
123
/** @internal */
119
124
sessionPool : ServerSessionPool ;
120
125
hasEnded : boolean ;
121
- clientOptions ? : MongoOptions ;
126
+ clientOptions : MongoOptions ;
122
127
supports : { causalConsistency : boolean } ;
123
128
clusterTime ?: ClusterTime ;
124
129
operationTime ?: Timestamp ;
@@ -140,6 +145,9 @@ export class ClientSession
140
145
/** @internal */
141
146
timeoutMS ?: number ;
142
147
148
+ /** @internal */
149
+ public timeoutContext : TimeoutContext | null = null ;
150
+
143
151
/**
144
152
* Create a client session.
145
153
* @internal
@@ -152,7 +160,7 @@ export class ClientSession
152
160
client : MongoClient ,
153
161
sessionPool : ServerSessionPool ,
154
162
options : ClientSessionOptions ,
155
- clientOptions ? : MongoOptions
163
+ clientOptions : MongoOptions
156
164
) {
157
165
super ( ) ;
158
166
@@ -272,7 +280,11 @@ export class ClientSession
272
280
async endSession ( options ?: EndSessionOptions ) : Promise < void > {
273
281
try {
274
282
if ( this . inTransaction ( ) ) {
275
- await this . abortTransaction ( ) ;
283
+ if ( typeof options ?. timeoutMS === 'number' ) {
284
+ await this . abortTransaction ( { timeoutMS : options . timeoutMS } ) ;
285
+ } else {
286
+ await this . abortTransaction ( ) ;
287
+ }
276
288
}
277
289
if ( ! this . hasEnded ) {
278
290
const serverSession = this [ kServerSession ] ;
@@ -291,6 +303,7 @@ export class ClientSession
291
303
}
292
304
} catch ( error ) {
293
305
// spec indicates that we should ignore all errors for `endSessions`
306
+ if ( MongoOperationTimeoutError . is ( error ) ) throw error ;
294
307
squashError ( error ) ;
295
308
} finally {
296
309
maybeClearPinnedConnection ( this , { force : true , ...options } ) ;
@@ -444,16 +457,20 @@ export class ClientSession
444
457
445
458
/**
446
459
* Commits the currently active transaction in this session.
460
+ *
461
+ * @param options - Optional options, can be used to override `defaultTimeoutMS`.
447
462
*/
448
- async commitTransaction ( ) : Promise < void > {
449
- return await endTransaction ( this , 'commitTransaction' ) ;
463
+ async commitTransaction ( options ?: { timeoutMS : number } ) : Promise < void > {
464
+ return await endTransaction ( this , 'commitTransaction' , options ) ;
450
465
}
451
466
452
467
/**
453
468
* Aborts the currently active transaction in this session.
469
+ *
470
+ * @param options - Optional options, can be used to override `defaultTimeoutMS`.
454
471
*/
455
- async abortTransaction ( ) : Promise < void > {
456
- return await endTransaction ( this , 'abortTransaction' ) ;
472
+ async abortTransaction ( options ?: { timeoutMS : number } ) : Promise < void > {
473
+ return await endTransaction ( this , 'abortTransaction' , options ) ;
457
474
}
458
475
459
476
/**
@@ -499,7 +516,15 @@ export class ClientSession
499
516
fn : WithTransactionCallback < T > ,
500
517
options ?: TransactionOptions
501
518
) : Promise < T > {
502
- const startTime = now ( ) ;
519
+ if ( typeof options ?. timeoutMS === 'number' )
520
+ this . timeoutContext = TimeoutContext . create ( {
521
+ timeoutMS : options . timeoutMS ,
522
+ serverSelectionTimeoutMS : this . clientOptions . serverSelectionTimeoutMS ,
523
+ socketTimeoutMS : this . clientOptions . socketTimeoutMS
524
+ } ) ;
525
+ const { timeoutContext } = this ;
526
+
527
+ const startTime = timeoutContext ?. csotEnabled ( ) ? timeoutContext . start : now ( ) ;
503
528
return await attemptTransaction ( this , startTime , fn , options ) ;
504
529
}
505
530
}
@@ -677,7 +702,8 @@ async function attemptTransaction<T>(
677
702
678
703
async function endTransaction (
679
704
session : ClientSession ,
680
- commandName : 'abortTransaction' | 'commitTransaction'
705
+ commandName : 'abortTransaction' | 'commitTransaction' ,
706
+ options : { timeoutMS ?: number } = { }
681
707
) : Promise < void > {
682
708
// handle any initial problematic cases
683
709
const txnState = session . transaction . state ;
@@ -749,6 +775,25 @@ async function endTransaction(
749
775
command . recoveryToken = session . transaction . recoveryToken ;
750
776
}
751
777
778
+ const timeoutMS =
779
+ 'timeoutMS' in options && typeof options . timeoutMS === 'number'
780
+ ? options . timeoutMS
781
+ : typeof session . timeoutMS === 'number'
782
+ ? session . timeoutMS
783
+ : session . timeoutContext ?. csotEnabled ( )
784
+ ? session . timeoutContext . timeoutMS
785
+ : null ;
786
+
787
+ const timeoutContext =
788
+ // override for this operation
789
+ TimeoutContext . create ( {
790
+ serverSelectionTimeoutMS : session . clientOptions . serverSelectionTimeoutMS ,
791
+ socketTimeoutMS : session . clientOptions . socketTimeoutMS ,
792
+ ...( timeoutMS != null
793
+ ? { timeoutMS }
794
+ : { waitQueueTimeoutMS : session . clientOptions . waitQueueTimeoutMS } )
795
+ } ) ;
796
+
752
797
try {
753
798
// send the command
754
799
await executeOperation (
@@ -757,7 +802,8 @@ async function endTransaction(
757
802
session,
758
803
readPreference : ReadPreference . primary ,
759
804
bypassPinningCheck : true
760
- } )
805
+ } ) ,
806
+ timeoutContext
761
807
) ;
762
808
if ( command . abortTransaction ) {
763
809
// always unpin on abort regardless of command outcome
@@ -794,7 +840,8 @@ async function endTransaction(
794
840
session,
795
841
readPreference : ReadPreference . primary ,
796
842
bypassPinningCheck : true
797
- } )
843
+ } ) ,
844
+ timeoutContext
798
845
) ;
799
846
if ( commandName !== 'commitTransaction' ) {
800
847
session . transaction . transition ( TxnState . TRANSACTION_ABORTED ) ;
@@ -824,6 +871,7 @@ function handleEndTransactionError(
824
871
maybeClearPinnedConnection ( session , { force : false } ) ;
825
872
}
826
873
// The spec indicates that if the operation times out or fails with a non-retryable error, we should ignore all errors on `abortTransaction`
874
+ if ( MongoOperationTimeoutError . is ( error ) ) throw error ; // But not if it is CSOT
827
875
return ;
828
876
}
829
877
0 commit comments