@@ -740,17 +740,30 @@ export class ClientSession
740740
741741 try {
742742 retryTransaction: for (
743- let attempt = 0 , isRetry = false ;
743+ // 2. Set `transactionAttempt` to `0`.
744+ let transactionAttempt = 0 , isRetry = false ;
744745 ! committed ;
745- ++ attempt , isRetry = attempt > 0
746+ ++ transactionAttempt , isRetry = transactionAttempt > 0
746747 ) {
748+ // 2. If `transactionAttempt` > 0:
747749 if ( isRetry ) {
750+ // 2.i If elapsed time + `backoffMS` > `TIMEOUT_MS`, then raise the previously encountered error. If the elapsed time of
751+ // `withTransaction` is less than TIMEOUT_MS, calculate the backoffMS to be
752+ // `jitter * min(BACKOFF_INITIAL * 1.5 ** (transactionAttempt - 1), BACKOFF_MAX)`. sleep for `backoffMS`.
753+ // 2.i.i jitter is a random float between \[0, 1)
754+ // 2.i.ii `transactionAttempt` is the variable defined in step 1.
755+ // 2.i.iii `BACKOFF_INITIAL` is 5ms
756+ // 2.i.iv `BACKOFF_MAX` is 500ms
748757 const BACKOFF_INITIAL_MS = 5 ;
749758 const BACKOFF_MAX_MS = 500 ;
750759 const BACKOFF_GROWTH = 1.5 ;
751760 const jitter = Math . random ( ) ;
752761 const backoffMS =
753- jitter * Math . min ( BACKOFF_INITIAL_MS * BACKOFF_GROWTH ** attempt , BACKOFF_MAX_MS ) ;
762+ jitter *
763+ Math . min (
764+ BACKOFF_INITIAL_MS * BACKOFF_GROWTH ** ( transactionAttempt - 1 ) ,
765+ BACKOFF_MAX_MS
766+ ) ;
754767
755768 const willExceedTransactionDeadline =
756769 ( this . timeoutContext ?. csotEnabled ( ) &&
@@ -769,13 +782,13 @@ export class ClientSession
769782 await setTimeout ( backoffMS ) ;
770783 }
771784
772- // 2 . Invoke startTransaction on the session
773- // 3 . If `startTransaction` reported an error, propagate that error to the caller of `withTransaction` and return immediately.
785+ // 3 . Invoke startTransaction on the session
786+ // 4 . If `startTransaction` reported an error, propagate that error to the caller of `withTransaction` and return immediately.
774787 this . startTransaction ( options ) ; // may throw on error
775788
776789 try {
777- // 4 . Invoke the callback.
778- // 5 . Control returns to withTransaction. (continued below)
790+ // 5 . Invoke the callback.
791+ // 6 . Control returns to withTransaction. (continued below)
779792 const promise = fn ( this ) ;
780793 if ( ! isPromiseLike ( promise ) ) {
781794 throw new MongoInvalidArgumentError (
@@ -785,18 +798,18 @@ export class ClientSession
785798
786799 result = await promise ;
787800
788- // 5 . (cont.) Determine the current state of the ClientSession (continued below)
801+ // 6 . (cont.) Determine the current state of the ClientSession (continued below)
789802 if (
790803 this . transaction . state === TxnState . NO_TRANSACTION ||
791804 this . transaction . state === TxnState . TRANSACTION_COMMITTED ||
792805 this . transaction . state === TxnState . TRANSACTION_ABORTED
793806 ) {
794- // 7 . If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed" state,
807+ // 8 . If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed" state,
795808 // assume the callback intentionally aborted or committed the transaction and return immediately.
796809 return result ;
797810 }
798811 // 5. (cont.) and whether the callback reported an error
799- // 6 . If the callback reported an error:
812+ // 7 . If the callback reported an error:
800813 } catch ( fnError ) {
801814 if ( ! ( fnError instanceof MongoError ) || fnError instanceof MongoInvalidArgumentError ) {
802815 // This first preemptive abort regardless of TxnState isn't spec,
@@ -809,7 +822,7 @@ export class ClientSession
809822 this . transaction . state === TxnState . STARTING_TRANSACTION ||
810823 this . transaction . state === TxnState . TRANSACTION_IN_PROGRESS
811824 ) {
812- // 6 .i If the ClientSession is in the "starting transaction" or "transaction in progress" state,
825+ // 7 .i If the ClientSession is in the "starting transaction" or "transaction in progress" state,
813826 // invoke abortTransaction on the session
814827 await this . abortTransaction ( ) ;
815828 }
@@ -818,16 +831,16 @@ export class ClientSession
818831 fnError . hasErrorLabel ( MongoErrorLabel . TransientTransactionError ) &&
819832 ( this . timeoutContext ?. csotEnabled ( ) || processTimeMS ( ) - startTime < MAX_TIMEOUT )
820833 ) {
821- // 6 .ii If the callback's error includes a "TransientTransactionError" label and the elapsed time of `withTransaction`
834+ // 7 .ii If the callback's error includes a "TransientTransactionError" label and the elapsed time of `withTransaction`
822835 // is less than 120 seconds, jump back to step two.
823836 lastError = fnError ;
824837 continue retryTransaction;
825838 }
826839
827- // 6 .iii If the callback's error includes a "UnknownTransactionCommitResult" label, the callback must have manually committed a transaction,
840+ // 7 .iii If the callback's error includes a "UnknownTransactionCommitResult" label, the callback must have manually committed a transaction,
828841 // propagate the callback's error to the caller of withTransaction and return immediately.
829- // The 6 .iii check is redundant with 6.iv, so we don't write code for it
830- // 6 .iv Otherwise, propagate the callback's error to the caller of withTransaction and return immediately.
842+ // The 7 .iii check is redundant with 6.iv, so we don't write code for it
843+ // 7 .iv Otherwise, propagate the callback's error to the caller of withTransaction and return immediately.
831844 throw fnError ;
832845 }
833846
@@ -838,10 +851,10 @@ export class ClientSession
838851 * apply a majority write concern if commitTransaction is
839852 * being retried (see: DRIVERS-601)
840853 */
841- // 8 . Invoke commitTransaction on the session.
854+ // 9 . Invoke commitTransaction on the session.
842855 await this . commitTransaction ( ) ;
843856 committed = true ;
844- // 9 . If commitTransaction reported an error:
857+ // 10 . If commitTransaction reported an error:
845858 } catch ( commitError ) {
846859 // If CSOT is enabled, we repeatedly retry until timeoutMS expires. This is enforced by providing a
847860 // timeoutContext to each async API, which know how to cancel themselves (i.e., the next retry will
@@ -862,21 +875,21 @@ export class ClientSession
862875 ! isMaxTimeMSExpiredError ( commitError ) &&
863876 commitError . hasErrorLabel ( MongoErrorLabel . UnknownTransactionCommitResult )
864877 ) {
865- // 9 .i If the `commitTransaction` error includes a "UnknownTransactionCommitResult" label and the error is not
878+ // 10 .i If the `commitTransaction` error includes a "UnknownTransactionCommitResult" label and the error is not
866879 // MaxTimeMSExpired and the elapsed time of `withTransaction` is less than 120 seconds, jump back to step eight.
867880 continue retryCommit;
868881 }
869882
870883 if ( commitError . hasErrorLabel ( MongoErrorLabel . TransientTransactionError ) ) {
871- // 9 .ii If the commitTransaction error includes a "TransientTransactionError" label
884+ // 10 .ii If the commitTransaction error includes a "TransientTransactionError" label
872885 // and the elapsed time of withTransaction is less than 120 seconds, jump back to step two.
873886 lastError = commitError ;
874887
875888 continue retryTransaction;
876889 }
877890 }
878891
879- // 9 .iii Otherwise, propagate the commitTransaction error to the caller of withTransaction and return immediately.
892+ // 10 .iii Otherwise, propagate the commitTransaction error to the caller of withTransaction and return immediately.
880893 throw commitError ;
881894 }
882895 }
0 commit comments