Skip to content

Commit c990750

Browse files
docs: annotate current withTransaction implementation with spec wording (#4826)
Co-authored-by: bailey <bailey.pearson@mongodb.com>
1 parent 4cb2b87 commit c990750

File tree

1 file changed

+28
-2
lines changed

1 file changed

+28
-2
lines changed

src/sessions.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,8 @@ export class ClientSession
726726
})
727727
: null;
728728

729-
const startTime = this.timeoutContext?.csotEnabled()
729+
// 1. Record the current monotonic time, which will be used to enforce the 120-second timeout before later retry attempts.
730+
const startTime = this.timeoutContext?.csotEnabled() // This is strictly to appease TS. We must narrow the context to a CSOT context before accessing `.start`.
730731
? this.timeoutContext.start
731732
: processTimeMS();
732733

@@ -735,9 +736,13 @@ export class ClientSession
735736

736737
try {
737738
while (!committed) {
739+
// 2. Invoke startTransaction on the session
740+
// 3. If `startTransaction` reported an error, propagate that error to the caller of `withTransaction` and return immediately.
738741
this.startTransaction(options); // may throw on error
739742

740743
try {
744+
// 4. Invoke the callback.
745+
// 5. Control returns to withTransaction. (continued below)
741746
const promise = fn(this);
742747
if (!isPromiseLike(promise)) {
743748
throw new MongoInvalidArgumentError(
@@ -747,16 +752,22 @@ export class ClientSession
747752

748753
result = await promise;
749754

755+
// 5. (cont.) Determine the current state of the ClientSession (continued below)
750756
if (
751757
this.transaction.state === TxnState.NO_TRANSACTION ||
752758
this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
753759
this.transaction.state === TxnState.TRANSACTION_ABORTED
754760
) {
755-
// Assume callback intentionally ended the transaction
761+
// 7. If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed" state,
762+
// assume the callback intentionally aborted or committed the transaction and return immediately.
756763
return result;
757764
}
765+
// 5. (cont.) and whether the callback reported an error
766+
// 6. If the callback reported an error:
758767
} catch (fnError) {
759768
if (!(fnError instanceof MongoError) || fnError instanceof MongoInvalidArgumentError) {
769+
// This first preemptive abort regardless of TxnState isn't spec,
770+
// and it's unclear whether it's serving a practical purpose, but this logic is OLD
760771
await this.abortTransaction();
761772
throw fnError;
762773
}
@@ -765,16 +776,24 @@ export class ClientSession
765776
this.transaction.state === TxnState.STARTING_TRANSACTION ||
766777
this.transaction.state === TxnState.TRANSACTION_IN_PROGRESS
767778
) {
779+
// 6.i If the ClientSession is in the "starting transaction" or "transaction in progress" state,
780+
// invoke abortTransaction on the session
768781
await this.abortTransaction();
769782
}
770783

771784
if (
772785
fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
773786
(this.timeoutContext != null || processTimeMS() - startTime < MAX_TIMEOUT)
774787
) {
788+
// 6.ii If the callback's error includes a "TransientTransactionError" label and the elapsed time of `withTransaction`
789+
// is less than 120 seconds, jump back to step two.
775790
continue;
776791
}
777792

793+
// 6.iii If the callback's error includes a "UnknownTransactionCommitResult" label, the callback must have manually committed a transaction,
794+
// propagate the callback's error to the caller of withTransaction and return immediately.
795+
// The 6.iii check is redundant with 6.iv, so we don't write code for it
796+
// 6.iv Otherwise, propagate the callback's error to the caller of withTransaction and return immediately.
778797
throw fnError;
779798
}
780799

@@ -785,8 +804,10 @@ export class ClientSession
785804
* apply a majority write concern if commitTransaction is
786805
* being retried (see: DRIVERS-601)
787806
*/
807+
// 8. Invoke commitTransaction on the session.
788808
await this.commitTransaction();
789809
committed = true;
810+
// 9. If commitTransaction reported an error:
790811
} catch (commitError) {
791812
/*
792813
* Note: a maxTimeMS error will have the MaxTimeMSExpired
@@ -800,16 +821,21 @@ export class ClientSession
800821
commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) &&
801822
(this.timeoutContext != null || processTimeMS() - startTime < MAX_TIMEOUT)
802823
) {
824+
// 9.i If the `commitTransaction` error includes a "UnknownTransactionCommitResult" label and the error is not
825+
// MaxTimeMSExpired and the elapsed time of `withTransaction` is less than 120 seconds, jump back to step eight.
803826
continue;
804827
}
805828

806829
if (
807830
commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
808831
(this.timeoutContext != null || processTimeMS() - startTime < MAX_TIMEOUT)
809832
) {
833+
// 9.ii If the commitTransaction error includes a "TransientTransactionError" label
834+
// and the elapsed time of withTransaction is less than 120 seconds, jump back to step two.
810835
break;
811836
}
812837

838+
// 9.iii Otherwise, propagate the commitTransaction error to the caller of withTransaction and return immediately.
813839
throw commitError;
814840
}
815841
}

0 commit comments

Comments
 (0)