Skip to content

Commit 65c8500

Browse files
committed
update to latest spec
1 parent cc6856f commit 65c8500

1 file changed

Lines changed: 36 additions & 22 deletions

File tree

src/sessions.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -739,16 +739,19 @@ export class ClientSession
739739
: null;
740740

741741
// 1. Define the following:
742-
// 1.1 Record the current monotonic time, which will be used to enforce the timeout before later retry attempts.
742+
// 1.1 Record the current monotonic time, which will be used to enforce the 120-second / CSOT timeout before later retry attempts.
743743
// 1.2 Set `transactionAttempt` to `0`.
744744
// 1.3 Set `TIMEOUT_MS` to be `timeoutMS` if given, otherwise MAX_TIMEOUT (120-seconds).
745745
//
746746
// The spec describes timeout checks as "elapsed time < TIMEOUT_MS" (where elapsed = now - start).
747747
// We precompute `deadline = start + TIMEOUT_MS` so each check becomes simply `now < deadline`.
748748
//
749-
// Note 1: When TIMEOUT_MS is reached, we MUST report a timeout error wrapping the last error that
750-
// triggered retry. With CSOT this is a MongoOperationTimeoutError; without CSOT the raw error
751-
// is propagated directly. See makeTimeoutError() below.
749+
// Timeout Error propagation mechanism
750+
// When the TIMEOUT_MS (calculated in step 1.3) is reached we MUST report a timeout error wrapping the previously
751+
// encountered error. If timeoutMS is set, then timeout error is a special type which is defined in CSOT
752+
// specification, If timeoutMS is not set, then propagate it as timeout error if the language allows to expose the
753+
// previously encountered error as a cause of a timeout error (see makeTimeoutError below in pseudo-code). If
754+
// timeout error is thrown then it SHOULD copy all error label(s) from the previously encountered retriable error.
752755
const csotEnabled = !!this.timeoutContext?.csotEnabled();
753756
const deadline = this.timeoutContext?.csotEnabled()
754757
? processTimeMS() + this.timeoutContext.remainingTimeMS
@@ -768,12 +771,16 @@ export class ClientSession
768771
) {
769772
// 2. If `transactionAttempt` > 0:
770773
if (isRetry) {
771-
// 2.1 Calculate backoffMS. If elapsed time + backoffMS > TIMEOUT_MS
772-
// (i.e., now + backoff >= deadline), raise the previously encountered error (see Note 1).
773-
// Otherwise, sleep for backoffMS.
774+
// 2.1 If elapsed time + backoffMS > TIMEOUT_MS, then propagate the previously encountered
775+
// error (see propagation section above). If the elapsed time of withTransaction is less
776+
// than TIMEOUT_MS, calculate the backoffMS to be
777+
// jitter * min(BACKOFF_INITIAL * 1.5 ** (transactionAttempt - 1), BACKOFF_MAX).
778+
// sleep for backoffMS.
774779
const BACKOFF_INITIAL_MS = 5;
775780
const BACKOFF_MAX_MS = 500;
776781
const BACKOFF_GROWTH = 1.5;
782+
// 2.1.1 Jitter is a random float between [0, 1), optionally including 1, depending on what is most natural
783+
// for the given driver language.
777784
const jitter = Math.random();
778785
const backoffMS =
779786
jitter *
@@ -795,20 +802,25 @@ export class ClientSession
795802
await setTimeout(backoffMS);
796803
}
797804

798-
// 3. Invoke startTransaction on the session and increment transactionAttempt.
805+
// 3. Invoke startTransaction on the session and increment transactionAttempt. If TransactionOptions were
806+
// specified in the call to withTransaction, those MUST be used for startTransaction. Note that
807+
// ClientSession.defaultTransactionOptions will be used in the absence of any explicit TransactionOptions.
799808
// 4. If startTransaction reported an error, propagate that error to the caller and return immediately.
800809
this.startTransaction(options); // may throw on error
801810

802811
try {
803-
// 5. Invoke the callback.
804-
// 6. Control returns to withTransaction. Determine the current state and whether the callback reported an error.
812+
// 5. Invoke the callback. Drivers MUST ensure that the ClientSession can be accessed within the callback
813+
// (e.g. pass ClientSession as the first parameter, rely on lexical scoping). Drivers MAY pass additional
814+
// parameters as needed (e.g. user data solicited by withTransaction).
805815
const promise = fn(this);
806816
if (!isPromiseLike(promise)) {
807817
throw new MongoInvalidArgumentError(
808818
'Function provided to `withTransaction` must return a Promise'
809819
);
810820
}
811821

822+
// 6. Control returns to withTransaction. Determine the current state of the ClientSession and whether the
823+
// callback reported an error (e.g. thrown exception, error output parameter).
812824
result = await promise;
813825

814826
// 8. If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed"
@@ -843,6 +855,9 @@ export class ClientSession
843855

844856
// 7.2 If the callback's error includes a "TransientTransactionError" label, jump back to step two.
845857
if (fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
858+
if (processTimeMS() >= deadline) {
859+
throw makeTimeoutError(lastError, csotEnabled);
860+
}
846861
continue retryTransaction;
847862
}
848863

@@ -864,29 +879,28 @@ export class ClientSession
864879
// 10. If commitTransaction reported an error:
865880
lastError = commitError;
866881

867-
// If elapsed time >= TIMEOUT_MS (i.e., now >= deadline), raise a timeout error (see Note 1).
868-
if (processTimeMS() >= deadline) {
869-
throw makeTimeoutError(commitError, csotEnabled);
870-
}
871-
872-
// 10.1 If the error includes "UnknownTransactionCommitResult" and is not MaxTimeMSExpired
873-
// and elapsed time < TIMEOUT_MS (guaranteed — deadline check above), jump back to step nine.
874-
// Note: a maxTimeMS error will have the MaxTimeMSExpired code (50) and can be reported
875-
// as a top-level error or inside writeConcernError.
882+
// 10.1 If the commitTransaction error includes a UnknownTransactionCommitResult label and the error is not MaxTimeMSExpired
876883
if (
877884
!isMaxTimeMSExpiredError(commitError) &&
878885
commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult)
879886
) {
887+
// 10.1.1 If the elapsed time of withTransaction exceeded TIMEOUT_MS, propagate the commitTransaction error to the caller
888+
// of withTransaction and return immediately (see propagation section above)
889+
if (processTimeMS() >= deadline) {
890+
throw makeTimeoutError(commitError, csotEnabled);
891+
}
892+
// 10.1.2 If the elapsed time of withTransaction is less than TIMEOUT_MS, jump back to step nine. We will trust
893+
// commitTransaction to apply a majority write concern on retry attempts (see: Majority write concern is used
894+
// when retrying commitTransaction).
880895
continue retryCommit;
881896
}
882897

883-
// 10.2 If the error includes "TransientTransactionError" and elapsed time < TIMEOUT_MS
884-
// (guaranteed — deadline check above), jump back to step two.
898+
// 10.2 If the commitTransaction error includes a "TransientTransactionError" label, jump back to step two.
885899
if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
886900
continue retryTransaction;
887901
}
888902

889-
// 10.3 Otherwise, propagate the commitTransaction error (see Note 1) and return immediately.
903+
// 10.3 Otherwise, propagate the commitTransaction error to the caller of withTransaction and return immediately.
890904
throw commitError;
891905
}
892906
}

0 commit comments

Comments
 (0)