mongodb 7.0.0-dev.20251218.sha.f0af829f → 7.0.0-dev.20251220.sha.e70fdc98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/change_stream.js +3 -1
- package/lib/change_stream.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +2 -1
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/handshake/client_metadata.js +1 -1
- package/lib/cmap/handshake/client_metadata.js.map +1 -1
- package/lib/mongo_client.js +1 -1
- package/lib/mongo_client.js.map +1 -1
- package/lib/operations/execute_operation.js +5 -4
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/sdam/server_selection.js +135 -72
- package/lib/sdam/server_selection.js.map +1 -1
- package/lib/sdam/topology.js +5 -4
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sessions.js +74 -38
- package/lib/sessions.js.map +1 -1
- package/mongodb.d.ts +2 -1
- package/package.json +1 -1
- package/src/change_stream.ts +3 -1
- package/src/cmap/auth/gssapi.ts +2 -1
- package/src/cmap/handshake/client_metadata.ts +1 -1
- package/src/index.ts +1 -1
- package/src/mongo_client.ts +2 -2
- package/src/operations/execute_operation.ts +6 -5
- package/src/sdam/server_selection.ts +187 -95
- package/src/sdam/topology.ts +14 -11
- package/src/sessions.ts +100 -44
package/src/sessions.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { setTimeout } from 'timers/promises';
|
|
2
|
+
|
|
1
3
|
import { Binary, type Document, Long, type Timestamp } from './bson';
|
|
2
4
|
import type { CommandOptions, Connection } from './cmap/connection';
|
|
3
5
|
import { ConnectionPoolMetrics } from './cmap/metrics';
|
|
@@ -732,17 +734,61 @@ export class ClientSession
|
|
|
732
734
|
: processTimeMS();
|
|
733
735
|
|
|
734
736
|
let committed = false;
|
|
735
|
-
let result:
|
|
737
|
+
let result: T;
|
|
738
|
+
|
|
739
|
+
let lastError: Error | null = null;
|
|
736
740
|
|
|
737
741
|
try {
|
|
738
|
-
|
|
739
|
-
// 2.
|
|
740
|
-
|
|
742
|
+
retryTransaction: for (
|
|
743
|
+
// 2. Set `transactionAttempt` to `0`.
|
|
744
|
+
let transactionAttempt = 0, isRetry = false;
|
|
745
|
+
!committed;
|
|
746
|
+
++transactionAttempt, isRetry = transactionAttempt > 0
|
|
747
|
+
) {
|
|
748
|
+
// 2. If `transactionAttempt` > 0:
|
|
749
|
+
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
|
|
757
|
+
const BACKOFF_INITIAL_MS = 5;
|
|
758
|
+
const BACKOFF_MAX_MS = 500;
|
|
759
|
+
const BACKOFF_GROWTH = 1.5;
|
|
760
|
+
const jitter = Math.random();
|
|
761
|
+
const backoffMS =
|
|
762
|
+
jitter *
|
|
763
|
+
Math.min(
|
|
764
|
+
BACKOFF_INITIAL_MS * BACKOFF_GROWTH ** (transactionAttempt - 1),
|
|
765
|
+
BACKOFF_MAX_MS
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
const willExceedTransactionDeadline =
|
|
769
|
+
(this.timeoutContext?.csotEnabled() &&
|
|
770
|
+
backoffMS > this.timeoutContext.remainingTimeMS) ||
|
|
771
|
+
processTimeMS() + backoffMS > startTime + MAX_TIMEOUT;
|
|
772
|
+
|
|
773
|
+
if (willExceedTransactionDeadline) {
|
|
774
|
+
throw (
|
|
775
|
+
lastError ??
|
|
776
|
+
new MongoRuntimeError(
|
|
777
|
+
`Transaction retry did not record an error: should never occur. Please file a bug.`
|
|
778
|
+
)
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
await setTimeout(backoffMS);
|
|
783
|
+
}
|
|
784
|
+
|
|
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.
|
|
741
787
|
this.startTransaction(options); // may throw on error
|
|
742
788
|
|
|
743
789
|
try {
|
|
744
|
-
//
|
|
745
|
-
//
|
|
790
|
+
// 5. Invoke the callback.
|
|
791
|
+
// 6. Control returns to withTransaction. (continued below)
|
|
746
792
|
const promise = fn(this);
|
|
747
793
|
if (!isPromiseLike(promise)) {
|
|
748
794
|
throw new MongoInvalidArgumentError(
|
|
@@ -752,18 +798,18 @@ export class ClientSession
|
|
|
752
798
|
|
|
753
799
|
result = await promise;
|
|
754
800
|
|
|
755
|
-
//
|
|
801
|
+
// 6. (cont.) Determine the current state of the ClientSession (continued below)
|
|
756
802
|
if (
|
|
757
803
|
this.transaction.state === TxnState.NO_TRANSACTION ||
|
|
758
804
|
this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
|
|
759
805
|
this.transaction.state === TxnState.TRANSACTION_ABORTED
|
|
760
806
|
) {
|
|
761
|
-
//
|
|
807
|
+
// 8. If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed" state,
|
|
762
808
|
// assume the callback intentionally aborted or committed the transaction and return immediately.
|
|
763
809
|
return result;
|
|
764
810
|
}
|
|
765
811
|
// 5. (cont.) and whether the callback reported an error
|
|
766
|
-
//
|
|
812
|
+
// 7. If the callback reported an error:
|
|
767
813
|
} catch (fnError) {
|
|
768
814
|
if (!(fnError instanceof MongoError) || fnError instanceof MongoInvalidArgumentError) {
|
|
769
815
|
// This first preemptive abort regardless of TxnState isn't spec,
|
|
@@ -776,70 +822,80 @@ export class ClientSession
|
|
|
776
822
|
this.transaction.state === TxnState.STARTING_TRANSACTION ||
|
|
777
823
|
this.transaction.state === TxnState.TRANSACTION_IN_PROGRESS
|
|
778
824
|
) {
|
|
779
|
-
//
|
|
825
|
+
// 7.i If the ClientSession is in the "starting transaction" or "transaction in progress" state,
|
|
780
826
|
// invoke abortTransaction on the session
|
|
781
827
|
await this.abortTransaction();
|
|
782
828
|
}
|
|
783
829
|
|
|
784
830
|
if (
|
|
785
831
|
fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
|
|
786
|
-
(this.timeoutContext
|
|
832
|
+
(this.timeoutContext?.csotEnabled() || processTimeMS() - startTime < MAX_TIMEOUT)
|
|
787
833
|
) {
|
|
788
|
-
//
|
|
834
|
+
// 7.ii If the callback's error includes a "TransientTransactionError" label and the elapsed time of `withTransaction`
|
|
789
835
|
// is less than 120 seconds, jump back to step two.
|
|
790
|
-
|
|
836
|
+
lastError = fnError;
|
|
837
|
+
continue retryTransaction;
|
|
791
838
|
}
|
|
792
839
|
|
|
793
|
-
//
|
|
840
|
+
// 7.iii If the callback's error includes a "UnknownTransactionCommitResult" label, the callback must have manually committed a transaction,
|
|
794
841
|
// propagate the callback's error to the caller of withTransaction and return immediately.
|
|
795
|
-
// The
|
|
796
|
-
//
|
|
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.
|
|
797
844
|
throw fnError;
|
|
798
845
|
}
|
|
799
846
|
|
|
800
|
-
while (!committed) {
|
|
847
|
+
retryCommit: while (!committed) {
|
|
801
848
|
try {
|
|
802
849
|
/*
|
|
803
850
|
* We will rely on ClientSession.commitTransaction() to
|
|
804
851
|
* apply a majority write concern if commitTransaction is
|
|
805
852
|
* being retried (see: DRIVERS-601)
|
|
806
853
|
*/
|
|
807
|
-
//
|
|
854
|
+
// 9. Invoke commitTransaction on the session.
|
|
808
855
|
await this.commitTransaction();
|
|
809
856
|
committed = true;
|
|
810
|
-
//
|
|
857
|
+
// 10. If commitTransaction reported an error:
|
|
811
858
|
} catch (commitError) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
if (
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
859
|
+
// If CSOT is enabled, we repeatedly retry until timeoutMS expires. This is enforced by providing a
|
|
860
|
+
// timeoutContext to each async API, which know how to cancel themselves (i.e., the next retry will
|
|
861
|
+
// abort the withTransaction call).
|
|
862
|
+
// If CSOT is not enabled, do we still have time remaining or have we timed out?
|
|
863
|
+
const hasTimedOut =
|
|
864
|
+
!this.timeoutContext?.csotEnabled() && processTimeMS() - startTime >= MAX_TIMEOUT;
|
|
865
|
+
|
|
866
|
+
if (!hasTimedOut) {
|
|
867
|
+
/*
|
|
868
|
+
* Note: a maxTimeMS error will have the MaxTimeMSExpired
|
|
869
|
+
* code (50) and can be reported as a top-level error or
|
|
870
|
+
* inside writeConcernError, ex.
|
|
871
|
+
* { ok:0, code: 50, codeName: 'MaxTimeMSExpired' }
|
|
872
|
+
* { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } }
|
|
873
|
+
*/
|
|
874
|
+
if (
|
|
875
|
+
!isMaxTimeMSExpiredError(commitError) &&
|
|
876
|
+
commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult)
|
|
877
|
+
) {
|
|
878
|
+
// 10.i If the `commitTransaction` error includes a "UnknownTransactionCommitResult" label and the error is not
|
|
879
|
+
// MaxTimeMSExpired and the elapsed time of `withTransaction` is less than 120 seconds, jump back to step eight.
|
|
880
|
+
continue retryCommit;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
|
|
884
|
+
// 10.ii If the commitTransaction error includes a "TransientTransactionError" label
|
|
885
|
+
// and the elapsed time of withTransaction is less than 120 seconds, jump back to step two.
|
|
886
|
+
lastError = commitError;
|
|
887
|
+
|
|
888
|
+
continue retryTransaction;
|
|
889
|
+
}
|
|
827
890
|
}
|
|
828
891
|
|
|
829
|
-
|
|
830
|
-
commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
|
|
831
|
-
(this.timeoutContext != null || processTimeMS() - startTime < MAX_TIMEOUT)
|
|
832
|
-
) {
|
|
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.
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// 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.
|
|
839
893
|
throw commitError;
|
|
840
894
|
}
|
|
841
895
|
}
|
|
842
896
|
}
|
|
897
|
+
|
|
898
|
+
// @ts-expect-error Result is always defined if we reach here, the for-loop above convinces TS it is not.
|
|
843
899
|
return result;
|
|
844
900
|
} finally {
|
|
845
901
|
this.timeoutContext = null;
|