@@ -23,6 +23,7 @@ import {
2323import type { MongoClient , MongoOptions } from './mongo_client' ;
2424import { TypedEventEmitter } from './mongo_types' ;
2525import { executeOperation } from './operations/execute_operation' ;
26+ import { RetryContext } from './operations/operation' ;
2627import { RunCommandOperation } from './operations/run_command' ;
2728import { ReadConcernLevel } from './read_concern' ;
2829import { ReadPreference } from './read_preference' ;
@@ -466,7 +467,11 @@ export class ClientSession
466467 } else {
467468 const wcKeys = Object . keys ( wc ) ;
468469 if ( wcKeys . length > 2 || ( ! wcKeys . includes ( 'wtimeoutMS' ) && ! wcKeys . includes ( 'wTimeoutMS' ) ) )
469- // if the write concern was specified with wTimeoutMS, then we set both wtimeoutMS and wTimeoutMS, guaranteeing at least two keys, so if we have more than two keys, then we can automatically assume that we should add the write concern to the command. If it has 2 or fewer keys, we need to check that those keys aren't the wtimeoutMS or wTimeoutMS options before we add the write concern to the command
470+ // if the write concern was specified with wTimeoutMS, then we set both wtimeoutMS
471+ // and wTimeoutMS, guaranteeing at least two keys, so if we have more than two keys,
472+ // then we can automatically assume that we should add the write concern to the command.
473+ // If it has 2 or fewer keys, we need to check that those keys aren't the wtimeoutMS
474+ // or wTimeoutMS options before we add the write concern to the command
470475 WriteConcern . apply ( command , { ...wc , wtimeoutMS : undefined } ) ;
471476 }
472477 }
@@ -487,11 +492,14 @@ export class ClientSession
487492 command . recoveryToken = this . transaction . recoveryToken ;
488493 }
489494
495+ const retryContext = new RetryContext ( 5 ) ;
496+
490497 const operation = new RunCommandOperation ( new MongoDBNamespace ( 'admin' ) , command , {
491498 session : this ,
492499 readPreference : ReadPreference . primary ,
493500 bypassPinningCheck : true
494501 } ) ;
502+ operation . retryContext = retryContext ;
495503
496504 const timeoutContext =
497505 this . timeoutContext ??
@@ -516,15 +524,13 @@ export class ClientSession
516524 this . unpin ( { force : true } ) ;
517525
518526 try {
519- await executeOperation (
520- this . client ,
521- new RunCommandOperation ( new MongoDBNamespace ( 'admin' ) , command , {
522- session : this ,
523- readPreference : ReadPreference . primary ,
524- bypassPinningCheck : true
525- } ) ,
526- timeoutContext
527- ) ;
527+ const op = new RunCommandOperation ( new MongoDBNamespace ( 'admin' ) , command , {
528+ session : this ,
529+ readPreference : ReadPreference . primary ,
530+ bypassPinningCheck : true
531+ } ) ;
532+ op . retryContext = retryContext ;
533+ await executeOperation ( this . client , op , timeoutContext ) ;
528534 return ;
529535 } catch ( retryCommitError ) {
530536 // If the retry failed, we process that error instead of the original
@@ -957,6 +963,11 @@ export class ServerSession {
957963 id : ServerSessionId ;
958964 lastUse : number ;
959965 txnNumber : number ;
966+
967+ /*
968+ * Indicates that a network error has been encountered while using this session.
969+ * Once a session is marked as dirty, it is always dirty.
970+ */
960971 isDirty : boolean ;
961972
962973 /** @internal */
@@ -1050,16 +1061,15 @@ export class ServerSessionPool {
10501061 * @param session - The session to release to the pool
10511062 */
10521063 release ( session : ServerSession ) : void {
1053- const sessionTimeoutMinutes = this . client . topology ?. logicalSessionTimeoutMinutes ?? 10 ;
1064+ if ( this . client . topology ?. loadBalanced ) {
1065+ if ( session . isDirty ) return ;
10541066
1055- if ( this . client . topology ?. loadBalanced && ! sessionTimeoutMinutes ) {
10561067 this . sessions . unshift ( session ) ;
1057- }
1058-
1059- if ( ! sessionTimeoutMinutes ) {
10601068 return ;
10611069 }
10621070
1071+ const sessionTimeoutMinutes = this . client . topology ?. logicalSessionTimeoutMinutes ?? 10 ;
1072+
10631073 this . sessions . prune ( session => session . hasTimedOut ( sessionTimeoutMinutes ) ) ;
10641074
10651075 if ( ! session . hasTimedOut ( sessionTimeoutMinutes ) ) {
@@ -1147,9 +1157,9 @@ export function applySession(
11471157 command . autocommit = false ;
11481158
11491159 if ( session . transaction . state === TxnState . STARTING_TRANSACTION ) {
1150- session . transaction . transition ( TxnState . TRANSACTION_IN_PROGRESS ) ;
11511160 command . startTransaction = true ;
11521161
1162+ // TODO: read concern only applied if it is not the same as the server's default
11531163 const readConcern =
11541164 session . transaction . options . readConcern || session ?. clientOptions ?. readConcern ;
11551165 if ( readConcern ) {
@@ -1185,4 +1195,17 @@ export function updateSessionFromResponse(session: ClientSession, document: Mong
11851195 session . snapshotTime = atClusterTime ;
11861196 }
11871197 }
1198+
1199+ if ( session . transaction . state === TxnState . STARTING_TRANSACTION ) {
1200+ if ( document . ok === 1 ) {
1201+ session . transaction . transition ( TxnState . TRANSACTION_IN_PROGRESS ) ;
1202+ } else {
1203+ const error = new MongoServerError ( document . toObject ( ) ) ;
1204+ const isBackpressureError = error . hasErrorLabel ( MongoErrorLabel . RetryableError ) ;
1205+
1206+ if ( ! isBackpressureError ) {
1207+ session . transaction . transition ( TxnState . TRANSACTION_IN_PROGRESS ) ;
1208+ }
1209+ }
1210+ }
11881211}
0 commit comments