mongodb 6.5.0 → 6.6.0-dev.20240504.sha.2609953
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/README.md +9 -9
- package/lib/admin.js +9 -9
- package/lib/admin.js.map +1 -1
- package/lib/bson.js +33 -22
- package/lib/bson.js.map +1 -1
- package/lib/bulk/common.js +13 -12
- package/lib/bulk/common.js.map +1 -1
- package/lib/change_stream.js +28 -18
- package/lib/change_stream.js.map +1 -1
- package/lib/client-side-encryption/auto_encrypter.js +2 -2
- package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js +6 -6
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/client-side-encryption/providers/aws.js +13 -10
- package/lib/client-side-encryption/providers/aws.js.map +1 -1
- package/lib/client-side-encryption/providers/azure.js +6 -3
- package/lib/client-side-encryption/providers/azure.js.map +1 -1
- package/lib/cmap/auth/aws_temporary_credentials.js +140 -0
- package/lib/cmap/auth/aws_temporary_credentials.js.map +1 -0
- package/lib/cmap/auth/gssapi.js +7 -6
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/auth/mongodb_aws.js +8 -101
- package/lib/cmap/auth/mongodb_aws.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +1 -1
- package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js +2 -1
- package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/service_workflow.js +1 -1
- package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -1
- package/lib/cmap/auth/scram.js +2 -2
- package/lib/cmap/auth/scram.js.map +1 -1
- package/lib/cmap/commands.js +24 -111
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connect.js +4 -4
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +61 -36
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +22 -13
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/cmap/handshake/client_metadata.js +2 -2
- package/lib/cmap/handshake/client_metadata.js.map +1 -1
- package/lib/cmap/wire_protocol/compression.js +8 -8
- package/lib/cmap/wire_protocol/compression.js.map +1 -1
- package/lib/cmap/wire_protocol/on_demand/document.js +218 -0
- package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -0
- package/lib/cmap/wire_protocol/responses.js +184 -0
- package/lib/cmap/wire_protocol/responses.js.map +1 -0
- package/lib/collection.js +42 -38
- package/lib/collection.js.map +1 -1
- package/lib/connection_string.js +4 -6
- package/lib/connection_string.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +76 -43
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/aggregation_cursor.js +16 -33
- package/lib/cursor/aggregation_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +36 -18
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/cursor/run_command_cursor.js +3 -2
- package/lib/cursor/run_command_cursor.js.map +1 -1
- package/lib/db.js +15 -19
- package/lib/db.js.map +1 -1
- package/lib/deps.js +31 -26
- package/lib/deps.js.map +1 -1
- package/lib/encrypter.js +14 -5
- package/lib/encrypter.js.map +1 -1
- package/lib/error.js +4 -3
- package/lib/error.js.map +1 -1
- package/lib/gridfs/download.js +19 -14
- package/lib/gridfs/download.js.map +1 -1
- package/lib/gridfs/index.js.map +1 -1
- package/lib/gridfs/upload.js +6 -1
- package/lib/gridfs/upload.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/mongo_client.js +11 -7
- package/lib/mongo_client.js.map +1 -1
- package/lib/mongo_logger.js +3 -0
- package/lib/mongo_logger.js.map +1 -1
- package/lib/operations/aggregate.js +2 -1
- package/lib/operations/aggregate.js.map +1 -1
- package/lib/operations/command.js +1 -1
- package/lib/operations/command.js.map +1 -1
- package/lib/operations/create_collection.js +1 -1
- package/lib/operations/create_collection.js.map +1 -1
- package/lib/operations/delete.js +4 -3
- package/lib/operations/delete.js.map +1 -1
- package/lib/operations/drop.js +1 -1
- package/lib/operations/drop.js.map +1 -1
- package/lib/operations/execute_operation.js +23 -8
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/find.js +4 -4
- package/lib/operations/find.js.map +1 -1
- package/lib/operations/get_more.js +2 -1
- package/lib/operations/get_more.js.map +1 -1
- package/lib/operations/indexes.js +29 -121
- package/lib/operations/indexes.js.map +1 -1
- package/lib/operations/insert.js +3 -3
- package/lib/operations/insert.js.map +1 -1
- package/lib/operations/kill_cursors.js +3 -1
- package/lib/operations/kill_cursors.js.map +1 -1
- package/lib/operations/list_collections.js +1 -1
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/operations/list_databases.js +1 -1
- package/lib/operations/list_databases.js.map +1 -1
- package/lib/operations/operation.js.map +1 -1
- package/lib/operations/run_command.js +4 -2
- package/lib/operations/run_command.js.map +1 -1
- package/lib/operations/search_indexes/create.js.map +1 -1
- package/lib/operations/stats.js +1 -1
- package/lib/operations/stats.js.map +1 -1
- package/lib/operations/update.js +1 -1
- package/lib/operations/update.js.map +1 -1
- package/lib/sdam/common.js.map +1 -1
- package/lib/sdam/monitor.js +139 -42
- package/lib/sdam/monitor.js.map +1 -1
- package/lib/sdam/server.js +5 -15
- package/lib/sdam/server.js.map +1 -1
- package/lib/sdam/server_description.js +1 -0
- package/lib/sdam/server_description.js.map +1 -1
- package/lib/sdam/server_selection.js +1 -1
- package/lib/sdam/server_selection.js.map +1 -1
- package/lib/sdam/srv_polling.js +2 -1
- package/lib/sdam/srv_polling.js.map +1 -1
- package/lib/sdam/topology.js +67 -54
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sdam/topology_description.js +10 -0
- package/lib/sdam/topology_description.js.map +1 -1
- package/lib/sessions.js +133 -93
- package/lib/sessions.js.map +1 -1
- package/lib/timeout.js +77 -0
- package/lib/timeout.js.map +1 -0
- package/lib/utils.js +61 -28
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +150 -38
- package/package.json +17 -14
- package/src/admin.ts +9 -9
- package/src/bson.ts +14 -0
- package/src/bulk/common.ts +3 -2
- package/src/change_stream.ts +39 -30
- package/src/client-side-encryption/auto_encrypter.ts +2 -2
- package/src/client-side-encryption/client_encryption.ts +6 -6
- package/src/client-side-encryption/providers/aws.ts +17 -10
- package/src/client-side-encryption/providers/azure.ts +5 -3
- package/src/cmap/auth/aws_temporary_credentials.ts +169 -0
- package/src/cmap/auth/gssapi.ts +9 -11
- package/src/cmap/auth/mongodb_aws.ts +19 -126
- package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc/callback_lock_cache.ts +2 -1
- package/src/cmap/auth/mongodb_oidc/service_workflow.ts +1 -1
- package/src/cmap/auth/scram.ts +2 -2
- package/src/cmap/commands.ts +28 -132
- package/src/cmap/connect.ts +4 -4
- package/src/cmap/connection.ts +107 -43
- package/src/cmap/connection_pool.ts +32 -29
- package/src/cmap/handshake/client_metadata.ts +2 -5
- package/src/cmap/wire_protocol/compression.ts +11 -13
- package/src/cmap/wire_protocol/on_demand/document.ts +338 -0
- package/src/cmap/wire_protocol/responses.ts +237 -0
- package/src/collection.ts +87 -58
- package/src/connection_string.ts +9 -7
- package/src/cursor/abstract_cursor.ts +102 -38
- package/src/cursor/aggregation_cursor.ts +32 -34
- package/src/cursor/find_cursor.ts +33 -21
- package/src/cursor/list_search_indexes_cursor.ts +1 -1
- package/src/cursor/run_command_cursor.ts +3 -2
- package/src/db.ts +42 -21
- package/src/deps.ts +52 -40
- package/src/encrypter.ts +14 -5
- package/src/error.ts +9 -3
- package/src/gridfs/download.ts +19 -31
- package/src/gridfs/index.ts +2 -0
- package/src/gridfs/upload.ts +11 -8
- package/src/index.ts +13 -5
- package/src/mongo_client.ts +21 -15
- package/src/mongo_logger.ts +3 -0
- package/src/mongo_types.ts +1 -1
- package/src/operations/aggregate.ts +2 -1
- package/src/operations/command.ts +1 -1
- package/src/operations/create_collection.ts +7 -2
- package/src/operations/delete.ts +4 -3
- package/src/operations/drop.ts +1 -1
- package/src/operations/execute_operation.ts +29 -10
- package/src/operations/find.ts +13 -14
- package/src/operations/get_more.ts +9 -1
- package/src/operations/indexes.ts +103 -176
- package/src/operations/insert.ts +2 -2
- package/src/operations/kill_cursors.ts +3 -2
- package/src/operations/list_collections.ts +5 -1
- package/src/operations/list_databases.ts +1 -1
- package/src/operations/operation.ts +3 -0
- package/src/operations/run_command.ts +6 -4
- package/src/operations/search_indexes/create.ts +4 -1
- package/src/operations/stats.ts +1 -1
- package/src/operations/update.ts +7 -7
- package/src/sdam/common.ts +8 -2
- package/src/sdam/monitor.ts +178 -61
- package/src/sdam/server.ts +27 -20
- package/src/sdam/server_description.ts +8 -3
- package/src/sdam/server_selection.ts +2 -3
- package/src/sdam/srv_polling.ts +3 -2
- package/src/sdam/topology.ts +114 -117
- package/src/sdam/topology_description.ts +14 -4
- package/src/sessions.ts +168 -148
- package/src/timeout.ts +96 -0
- package/src/utils.ts +85 -32
- package/lib/operations/common_functions.js +0 -38
- package/lib/operations/common_functions.js.map +0 -1
- package/src/operations/common_functions.ts +0 -79
package/src/sessions.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { promisify } from 'util';
|
|
2
|
-
|
|
3
1
|
import { Binary, type Document, Long, type Timestamp } from './bson';
|
|
4
2
|
import type { CommandOptions, Connection } from './cmap/connection';
|
|
5
3
|
import { ConnectionPoolMetrics } from './cmap/metrics';
|
|
4
|
+
import { type MongoDBResponse } from './cmap/wire_protocol/responses';
|
|
6
5
|
import { isSharded } from './cmap/wire_protocol/shared';
|
|
7
6
|
import { PINNED, UNPINNED } from './constants';
|
|
8
7
|
import type { AbstractCursor } from './cursor/abstract_cursor';
|
|
@@ -38,12 +37,12 @@ import {
|
|
|
38
37
|
import {
|
|
39
38
|
ByteUtils,
|
|
40
39
|
calculateDurationInMs,
|
|
41
|
-
type Callback,
|
|
42
40
|
commandSupportsReadConcern,
|
|
43
41
|
isPromiseLike,
|
|
44
42
|
List,
|
|
45
43
|
maxWireVersion,
|
|
46
44
|
now,
|
|
45
|
+
squashError,
|
|
47
46
|
uuidV4
|
|
48
47
|
} from './utils';
|
|
49
48
|
import { WriteConcern } from './write_concern';
|
|
@@ -58,6 +57,9 @@ export interface ClientSessionOptions {
|
|
|
58
57
|
snapshot?: boolean;
|
|
59
58
|
/** The default TransactionOptions to use for transactions started on this session. */
|
|
60
59
|
defaultTransactionOptions?: TransactionOptions;
|
|
60
|
+
/** @internal
|
|
61
|
+
* The value of timeoutMS used for CSOT. Used to override client timeoutMS */
|
|
62
|
+
defaultTimeoutMS?: number;
|
|
61
63
|
|
|
62
64
|
/** @internal */
|
|
63
65
|
owner?: symbol | AbstractCursor;
|
|
@@ -128,6 +130,8 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
|
|
|
128
130
|
[kPinnedConnection]?: Connection;
|
|
129
131
|
/** @internal */
|
|
130
132
|
[kTxnNumberIncrement]: number;
|
|
133
|
+
/** @internal */
|
|
134
|
+
timeoutMS?: number;
|
|
131
135
|
|
|
132
136
|
/**
|
|
133
137
|
* Create a client session.
|
|
@@ -170,6 +174,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
|
|
|
170
174
|
this.sessionPool = sessionPool;
|
|
171
175
|
this.hasEnded = false;
|
|
172
176
|
this.clientOptions = clientOptions;
|
|
177
|
+
this.timeoutMS = options.defaultTimeoutMS ?? client.options?.timeoutMS;
|
|
173
178
|
|
|
174
179
|
this.explicit = !!options.explicit;
|
|
175
180
|
this[kServerSession] = this.explicit ? this.sessionPool.acquire() : null;
|
|
@@ -185,7 +190,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
|
|
|
185
190
|
|
|
186
191
|
this.operationTime = undefined;
|
|
187
192
|
this.owner = options.owner;
|
|
188
|
-
this.defaultTransactionOptions =
|
|
193
|
+
this.defaultTransactionOptions = { ...options.defaultTransactionOptions };
|
|
189
194
|
this.transaction = new Transaction();
|
|
190
195
|
}
|
|
191
196
|
|
|
@@ -274,8 +279,9 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
|
|
|
274
279
|
this.hasEnded = true;
|
|
275
280
|
this.emit('ended', this);
|
|
276
281
|
}
|
|
277
|
-
} catch {
|
|
282
|
+
} catch (error) {
|
|
278
283
|
// spec indicates that we should ignore all errors for `endSessions`
|
|
284
|
+
squashError(error);
|
|
279
285
|
} finally {
|
|
280
286
|
maybeClearPinnedConnection(this, { force: true, ...options });
|
|
281
287
|
}
|
|
@@ -415,14 +421,14 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
|
|
|
415
421
|
* Commits the currently active transaction in this session.
|
|
416
422
|
*/
|
|
417
423
|
async commitTransaction(): Promise<void> {
|
|
418
|
-
return
|
|
424
|
+
return await endTransaction(this, 'commitTransaction');
|
|
419
425
|
}
|
|
420
426
|
|
|
421
427
|
/**
|
|
422
428
|
* Aborts the currently active transaction in this session.
|
|
423
429
|
*/
|
|
424
430
|
async abortTransaction(): Promise<void> {
|
|
425
|
-
return
|
|
431
|
+
return await endTransaction(this, 'abortTransaction');
|
|
426
432
|
}
|
|
427
433
|
|
|
428
434
|
/**
|
|
@@ -435,18 +441,26 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
|
|
|
435
441
|
/**
|
|
436
442
|
* Starts a transaction and runs a provided function, ensuring the commitTransaction is always attempted when all operations run in the function have completed.
|
|
437
443
|
*
|
|
438
|
-
* **IMPORTANT:** This method requires the
|
|
444
|
+
* **IMPORTANT:** This method requires the function passed in to return a Promise. That promise must be made by `await`-ing all operations in such a way that rejections are propagated to the returned promise.
|
|
439
445
|
*
|
|
440
446
|
* @remarks
|
|
441
|
-
*
|
|
442
|
-
* - If
|
|
443
|
-
* - If the transaction is unable to complete or an error is thrown from within the provided function, then this function will throw an error.
|
|
447
|
+
* - If all operations successfully complete and the `commitTransaction` operation is successful, then the provided function will return the result of the provided function.
|
|
448
|
+
* - If the transaction is unable to complete or an error is thrown from within the provided function, then the provided function will throw an error.
|
|
444
449
|
* - If the transaction is manually aborted within the provided function it will not throw.
|
|
445
|
-
* -
|
|
450
|
+
* - If the driver needs to attempt to retry the operations, the provided function may be called multiple times.
|
|
446
451
|
*
|
|
447
452
|
* Checkout a descriptive example here:
|
|
448
453
|
* @see https://www.mongodb.com/blog/post/quick-start-nodejs--mongodb--how-to-implement-transactions
|
|
449
454
|
*
|
|
455
|
+
* If a command inside withTransaction fails:
|
|
456
|
+
* - It may cause the transaction on the server to be aborted.
|
|
457
|
+
* - This situation is normally handled transparently by the driver.
|
|
458
|
+
* - However, if the application catches such an error and does not rethrow it, the driver will not be able to determine whether the transaction was aborted or not.
|
|
459
|
+
* - The driver will then retry the transaction indefinitely.
|
|
460
|
+
*
|
|
461
|
+
* To avoid this situation, the application must not silently handle errors within the provided function.
|
|
462
|
+
* If the application needs to handle errors within, it must await all operations such that if an operation is rejected it becomes the rejection of the callback function passed into withTransaction.
|
|
463
|
+
*
|
|
450
464
|
* @param fn - callback to run within a transaction
|
|
451
465
|
* @param options - optional settings for the transaction
|
|
452
466
|
* @returns A raw command response or undefined
|
|
@@ -456,7 +470,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
|
|
|
456
470
|
options?: TransactionOptions
|
|
457
471
|
): Promise<T> {
|
|
458
472
|
const startTime = now();
|
|
459
|
-
return attemptTransaction(this, startTime, fn, options);
|
|
473
|
+
return await attemptTransaction(this, startTime, fn, options);
|
|
460
474
|
}
|
|
461
475
|
}
|
|
462
476
|
|
|
@@ -511,6 +525,7 @@ export function maybeClearPinnedConnection(
|
|
|
511
525
|
|
|
512
526
|
if (options?.error == null || options?.force) {
|
|
513
527
|
loadBalancer.pool.checkIn(conn);
|
|
528
|
+
session[kPinnedConnection] = undefined;
|
|
514
529
|
conn.emit(
|
|
515
530
|
UNPINNED,
|
|
516
531
|
session.transaction.state !== TxnState.NO_TRANSACTION
|
|
@@ -522,8 +537,6 @@ export function maybeClearPinnedConnection(
|
|
|
522
537
|
loadBalancer.pool.clear({ serviceId: conn.serviceId });
|
|
523
538
|
}
|
|
524
539
|
}
|
|
525
|
-
|
|
526
|
-
session[kPinnedConnection] = undefined;
|
|
527
540
|
}
|
|
528
541
|
}
|
|
529
542
|
|
|
@@ -538,33 +551,33 @@ function isMaxTimeMSExpiredError(err: MongoError) {
|
|
|
538
551
|
);
|
|
539
552
|
}
|
|
540
553
|
|
|
541
|
-
function attemptTransactionCommit<T>(
|
|
554
|
+
async function attemptTransactionCommit<T>(
|
|
542
555
|
session: ClientSession,
|
|
543
556
|
startTime: number,
|
|
544
557
|
fn: WithTransactionCallback<T>,
|
|
545
|
-
result:
|
|
558
|
+
result: T,
|
|
546
559
|
options: TransactionOptions
|
|
547
560
|
): Promise<T> {
|
|
548
|
-
|
|
549
|
-
()
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (err.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
|
|
561
|
-
return attemptTransaction(session, startTime, fn, options);
|
|
562
|
-
}
|
|
561
|
+
try {
|
|
562
|
+
await session.commitTransaction();
|
|
563
|
+
return result;
|
|
564
|
+
} catch (commitErr) {
|
|
565
|
+
if (
|
|
566
|
+
commitErr instanceof MongoError &&
|
|
567
|
+
hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT) &&
|
|
568
|
+
!isMaxTimeMSExpiredError(commitErr)
|
|
569
|
+
) {
|
|
570
|
+
if (commitErr.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult)) {
|
|
571
|
+
return await attemptTransactionCommit(session, startTime, fn, result, options);
|
|
563
572
|
}
|
|
564
573
|
|
|
565
|
-
|
|
574
|
+
if (commitErr.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
|
|
575
|
+
return await attemptTransaction(session, startTime, fn, options);
|
|
576
|
+
}
|
|
566
577
|
}
|
|
567
|
-
|
|
578
|
+
|
|
579
|
+
throw commitErr;
|
|
580
|
+
}
|
|
568
581
|
}
|
|
569
582
|
|
|
570
583
|
const USER_EXPLICIT_TXN_END_STATES = new Set<TxnState>([
|
|
@@ -577,12 +590,12 @@ function userExplicitlyEndedTransaction(session: ClientSession) {
|
|
|
577
590
|
return USER_EXPLICIT_TXN_END_STATES.has(session.transaction.state);
|
|
578
591
|
}
|
|
579
592
|
|
|
580
|
-
function attemptTransaction<T>(
|
|
593
|
+
async function attemptTransaction<T>(
|
|
581
594
|
session: ClientSession,
|
|
582
595
|
startTime: number,
|
|
583
596
|
fn: WithTransactionCallback<T>,
|
|
584
597
|
options: TransactionOptions = {}
|
|
585
|
-
): Promise<
|
|
598
|
+
): Promise<T> {
|
|
586
599
|
session.startTransaction(options);
|
|
587
600
|
|
|
588
601
|
let promise;
|
|
@@ -593,65 +606,52 @@ function attemptTransaction<T>(
|
|
|
593
606
|
}
|
|
594
607
|
|
|
595
608
|
if (!isPromiseLike(promise)) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
609
|
+
try {
|
|
610
|
+
await session.abortTransaction();
|
|
611
|
+
} catch (error) {
|
|
612
|
+
squashError(error);
|
|
613
|
+
}
|
|
614
|
+
throw new MongoInvalidArgumentError(
|
|
615
|
+
'Function provided to `withTransaction` must return a Promise'
|
|
599
616
|
);
|
|
600
617
|
}
|
|
601
618
|
|
|
602
|
-
|
|
603
|
-
result
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (
|
|
613
|
-
err instanceof MongoError &&
|
|
614
|
-
err.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
|
|
615
|
-
hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT)
|
|
616
|
-
) {
|
|
617
|
-
return attemptTransaction(session, startTime, fn, options);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (isMaxTimeMSExpiredError(err)) {
|
|
621
|
-
err.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
throw err;
|
|
625
|
-
}
|
|
619
|
+
try {
|
|
620
|
+
const result = await promise;
|
|
621
|
+
if (userExplicitlyEndedTransaction(session)) {
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
return await attemptTransactionCommit(session, startTime, fn, result, options);
|
|
625
|
+
} catch (err) {
|
|
626
|
+
if (session.inTransaction()) {
|
|
627
|
+
await session.abortTransaction();
|
|
628
|
+
}
|
|
626
629
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
+
if (
|
|
631
|
+
err instanceof MongoError &&
|
|
632
|
+
err.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
|
|
633
|
+
hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT)
|
|
634
|
+
) {
|
|
635
|
+
return await attemptTransaction(session, startTime, fn, options);
|
|
636
|
+
}
|
|
630
637
|
|
|
631
|
-
|
|
638
|
+
if (isMaxTimeMSExpiredError(err)) {
|
|
639
|
+
err.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
|
|
632
640
|
}
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
641
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
commandName: 'abortTransaction' | 'commitTransaction',
|
|
640
|
-
callback: (error: Error) => void
|
|
641
|
-
) => void
|
|
642
|
-
);
|
|
642
|
+
throw err;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
643
645
|
|
|
644
|
-
function endTransaction(
|
|
646
|
+
async function endTransaction(
|
|
645
647
|
session: ClientSession,
|
|
646
|
-
commandName: 'abortTransaction' | 'commitTransaction'
|
|
647
|
-
|
|
648
|
-
) {
|
|
648
|
+
commandName: 'abortTransaction' | 'commitTransaction'
|
|
649
|
+
): Promise<void> {
|
|
649
650
|
// handle any initial problematic cases
|
|
650
651
|
const txnState = session.transaction.state;
|
|
651
652
|
|
|
652
653
|
if (txnState === TxnState.NO_TRANSACTION) {
|
|
653
|
-
|
|
654
|
-
return;
|
|
654
|
+
throw new MongoTransactionError('No transaction started');
|
|
655
655
|
}
|
|
656
656
|
|
|
657
657
|
if (commandName === 'commitTransaction') {
|
|
@@ -661,37 +661,32 @@ function endTransaction(
|
|
|
661
661
|
) {
|
|
662
662
|
// the transaction was never started, we can safely exit here
|
|
663
663
|
session.transaction.transition(TxnState.TRANSACTION_COMMITTED_EMPTY);
|
|
664
|
-
callback();
|
|
665
664
|
return;
|
|
666
665
|
}
|
|
667
666
|
|
|
668
667
|
if (txnState === TxnState.TRANSACTION_ABORTED) {
|
|
669
|
-
|
|
670
|
-
|
|
668
|
+
throw new MongoTransactionError(
|
|
669
|
+
'Cannot call commitTransaction after calling abortTransaction'
|
|
671
670
|
);
|
|
672
|
-
return;
|
|
673
671
|
}
|
|
674
672
|
} else {
|
|
675
673
|
if (txnState === TxnState.STARTING_TRANSACTION) {
|
|
676
674
|
// the transaction was never started, we can safely exit here
|
|
677
675
|
session.transaction.transition(TxnState.TRANSACTION_ABORTED);
|
|
678
|
-
callback();
|
|
679
676
|
return;
|
|
680
677
|
}
|
|
681
678
|
|
|
682
679
|
if (txnState === TxnState.TRANSACTION_ABORTED) {
|
|
683
|
-
|
|
684
|
-
return;
|
|
680
|
+
throw new MongoTransactionError('Cannot call abortTransaction twice');
|
|
685
681
|
}
|
|
686
682
|
|
|
687
683
|
if (
|
|
688
684
|
txnState === TxnState.TRANSACTION_COMMITTED ||
|
|
689
685
|
txnState === TxnState.TRANSACTION_COMMITTED_EMPTY
|
|
690
686
|
) {
|
|
691
|
-
|
|
692
|
-
|
|
687
|
+
throw new MongoTransactionError(
|
|
688
|
+
'Cannot call abortTransaction after calling commitTransaction'
|
|
693
689
|
);
|
|
694
|
-
return;
|
|
695
690
|
}
|
|
696
691
|
}
|
|
697
692
|
|
|
@@ -718,49 +713,38 @@ function endTransaction(
|
|
|
718
713
|
Object.assign(command, { maxTimeMS: session.transaction.options.maxTimeMS });
|
|
719
714
|
}
|
|
720
715
|
|
|
721
|
-
|
|
716
|
+
if (session.transaction.recoveryToken) {
|
|
717
|
+
command.recoveryToken = session.transaction.recoveryToken;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
try {
|
|
721
|
+
// send the command
|
|
722
|
+
await executeOperation(
|
|
723
|
+
session.client,
|
|
724
|
+
new RunAdminCommandOperation(command, {
|
|
725
|
+
session,
|
|
726
|
+
readPreference: ReadPreference.primary,
|
|
727
|
+
bypassPinningCheck: true
|
|
728
|
+
})
|
|
729
|
+
);
|
|
730
|
+
if (command.abortTransaction) {
|
|
731
|
+
// always unpin on abort regardless of command outcome
|
|
732
|
+
session.unpin();
|
|
733
|
+
}
|
|
722
734
|
if (commandName !== 'commitTransaction') {
|
|
723
735
|
session.transaction.transition(TxnState.TRANSACTION_ABORTED);
|
|
724
736
|
if (session.loadBalanced) {
|
|
725
737
|
maybeClearPinnedConnection(session, { force: false });
|
|
726
738
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
return callback();
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
|
|
733
|
-
if (error instanceof MongoError) {
|
|
734
|
-
if (
|
|
735
|
-
isRetryableWriteError(error) ||
|
|
736
|
-
error instanceof MongoWriteConcernError ||
|
|
737
|
-
isMaxTimeMSExpiredError(error)
|
|
738
|
-
) {
|
|
739
|
-
if (isUnknownTransactionCommitResult(error)) {
|
|
740
|
-
error.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
|
|
741
|
-
|
|
742
|
-
// per txns spec, must unpin session in this case
|
|
743
|
-
session.unpin({ error });
|
|
744
|
-
}
|
|
745
|
-
} else if (error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
|
|
746
|
-
session.unpin({ error });
|
|
747
|
-
}
|
|
739
|
+
} else {
|
|
740
|
+
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
|
|
748
741
|
}
|
|
749
|
-
|
|
750
|
-
callback(error);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
if (session.transaction.recoveryToken) {
|
|
754
|
-
command.recoveryToken = session.transaction.recoveryToken;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
const handleFirstCommandAttempt = (error?: Error) => {
|
|
742
|
+
} catch (firstAttemptErr) {
|
|
758
743
|
if (command.abortTransaction) {
|
|
759
744
|
// always unpin on abort regardless of command outcome
|
|
760
745
|
session.unpin();
|
|
761
746
|
}
|
|
762
|
-
|
|
763
|
-
if (error instanceof MongoError && isRetryableWriteError(error)) {
|
|
747
|
+
if (firstAttemptErr instanceof MongoError && isRetryableWriteError(firstAttemptErr)) {
|
|
764
748
|
// SPEC-1185: apply majority write concern when retrying commitTransaction
|
|
765
749
|
if (command.commitTransaction) {
|
|
766
750
|
// per txns spec, must unpin session in this case
|
|
@@ -771,29 +755,65 @@ function endTransaction(
|
|
|
771
755
|
});
|
|
772
756
|
}
|
|
773
757
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
758
|
+
try {
|
|
759
|
+
await executeOperation(
|
|
760
|
+
session.client,
|
|
761
|
+
new RunAdminCommandOperation(command, {
|
|
762
|
+
session,
|
|
763
|
+
readPreference: ReadPreference.primary,
|
|
764
|
+
bypassPinningCheck: true
|
|
765
|
+
})
|
|
766
|
+
);
|
|
767
|
+
if (commandName !== 'commitTransaction') {
|
|
768
|
+
session.transaction.transition(TxnState.TRANSACTION_ABORTED);
|
|
769
|
+
if (session.loadBalanced) {
|
|
770
|
+
maybeClearPinnedConnection(session, { force: false });
|
|
771
|
+
}
|
|
772
|
+
} else {
|
|
773
|
+
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
|
|
774
|
+
}
|
|
775
|
+
} catch (secondAttemptErr) {
|
|
776
|
+
handleEndTransactionError(session, commandName, secondAttemptErr);
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
handleEndTransactionError(session, commandName, firstAttemptErr);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function handleEndTransactionError(
|
|
785
|
+
session: ClientSession,
|
|
786
|
+
commandName: 'abortTransaction' | 'commitTransaction',
|
|
787
|
+
error: Error
|
|
788
|
+
) {
|
|
789
|
+
if (commandName !== 'commitTransaction') {
|
|
790
|
+
session.transaction.transition(TxnState.TRANSACTION_ABORTED);
|
|
791
|
+
if (session.loadBalanced) {
|
|
792
|
+
maybeClearPinnedConnection(session, { force: false });
|
|
783
793
|
}
|
|
794
|
+
// The spec indicates that if the operation times out or fails with a non-retryable error, we should ignore all errors on `abortTransaction`
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
|
|
799
|
+
if (error instanceof MongoError) {
|
|
800
|
+
if (
|
|
801
|
+
isRetryableWriteError(error) ||
|
|
802
|
+
error instanceof MongoWriteConcernError ||
|
|
803
|
+
isMaxTimeMSExpiredError(error)
|
|
804
|
+
) {
|
|
805
|
+
if (isUnknownTransactionCommitResult(error)) {
|
|
806
|
+
error.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
|
|
807
|
+
|
|
808
|
+
// per txns spec, must unpin session in this case
|
|
809
|
+
session.unpin({ error });
|
|
810
|
+
}
|
|
811
|
+
} else if (error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
|
|
812
|
+
session.unpin({ error });
|
|
813
|
+
}
|
|
814
|
+
}
|
|
784
815
|
|
|
785
|
-
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
// send the command
|
|
789
|
-
executeOperation(
|
|
790
|
-
session.client,
|
|
791
|
-
new RunAdminCommandOperation(command, {
|
|
792
|
-
session,
|
|
793
|
-
readPreference: ReadPreference.primary,
|
|
794
|
-
bypassPinningCheck: true
|
|
795
|
-
})
|
|
796
|
-
).then(() => handleFirstCommandAttempt(), handleFirstCommandAttempt);
|
|
816
|
+
throw error;
|
|
797
817
|
}
|
|
798
818
|
|
|
799
819
|
/** @public */
|
|
@@ -1030,7 +1050,7 @@ export function applySession(
|
|
|
1030
1050
|
return;
|
|
1031
1051
|
}
|
|
1032
1052
|
|
|
1033
|
-
export function updateSessionFromResponse(session: ClientSession, document:
|
|
1053
|
+
export function updateSessionFromResponse(session: ClientSession, document: MongoDBResponse): void {
|
|
1034
1054
|
if (document.$clusterTime) {
|
|
1035
1055
|
_advanceClusterTime(session, document.$clusterTime);
|
|
1036
1056
|
}
|
|
@@ -1046,7 +1066,7 @@ export function updateSessionFromResponse(session: ClientSession, document: Docu
|
|
|
1046
1066
|
if (session?.[kSnapshotEnabled] && session[kSnapshotTime] == null) {
|
|
1047
1067
|
// find and aggregate commands return atClusterTime on the cursor
|
|
1048
1068
|
// distinct includes it in the response body
|
|
1049
|
-
const atClusterTime = document.
|
|
1069
|
+
const atClusterTime = document.atClusterTime;
|
|
1050
1070
|
if (atClusterTime) {
|
|
1051
1071
|
session[kSnapshotTime] = atClusterTime;
|
|
1052
1072
|
}
|
package/src/timeout.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { clearTimeout, setTimeout } from 'timers';
|
|
2
|
+
|
|
3
|
+
import { MongoInvalidArgumentError } from './error';
|
|
4
|
+
import { noop } from './utils';
|
|
5
|
+
|
|
6
|
+
/** @internal */
|
|
7
|
+
export class TimeoutError extends Error {
|
|
8
|
+
override get name(): 'TimeoutError' {
|
|
9
|
+
return 'TimeoutError';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
constructor(message: string, options?: { cause?: Error }) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static is(error: unknown): error is TimeoutError {
|
|
17
|
+
return (
|
|
18
|
+
error != null && typeof error === 'object' && 'name' in error && error.name === 'TimeoutError'
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type Executor = ConstructorParameters<typeof Promise<never>>[0];
|
|
24
|
+
type Reject = Parameters<ConstructorParameters<typeof Promise<never>>[0]>[1];
|
|
25
|
+
/**
|
|
26
|
+
* @internal
|
|
27
|
+
* This class is an abstraction over timeouts
|
|
28
|
+
* The Timeout class can only be in the pending or rejected states. It is guaranteed not to resolve
|
|
29
|
+
* if interacted with exclusively through its public API
|
|
30
|
+
* */
|
|
31
|
+
export class Timeout extends Promise<never> {
|
|
32
|
+
get [Symbol.toStringTag](): 'MongoDBTimeout' {
|
|
33
|
+
return 'MongoDBTimeout';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private id?: NodeJS.Timeout;
|
|
37
|
+
|
|
38
|
+
public readonly start: number;
|
|
39
|
+
public ended: number | null = null;
|
|
40
|
+
public duration: number;
|
|
41
|
+
public timedOut = false;
|
|
42
|
+
|
|
43
|
+
/** Create a new timeout that expires in `duration` ms */
|
|
44
|
+
private constructor(executor: Executor = () => null, duration: number) {
|
|
45
|
+
let reject!: Reject;
|
|
46
|
+
|
|
47
|
+
if (duration < 0) {
|
|
48
|
+
throw new MongoInvalidArgumentError('Cannot create a Timeout with a negative duration');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
super((_, promiseReject) => {
|
|
52
|
+
reject = promiseReject;
|
|
53
|
+
|
|
54
|
+
executor(noop, promiseReject);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.duration = duration;
|
|
58
|
+
this.start = Math.trunc(performance.now());
|
|
59
|
+
|
|
60
|
+
if (this.duration > 0) {
|
|
61
|
+
this.id = setTimeout(() => {
|
|
62
|
+
this.ended = Math.trunc(performance.now());
|
|
63
|
+
this.timedOut = true;
|
|
64
|
+
reject(new TimeoutError(`Expired after ${duration}ms`));
|
|
65
|
+
}, this.duration);
|
|
66
|
+
// Ensure we do not keep the Node.js event loop running
|
|
67
|
+
if (typeof this.id.unref === 'function') {
|
|
68
|
+
this.id.unref();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Clears the underlying timeout. This method is idempotent
|
|
75
|
+
*/
|
|
76
|
+
clear(): void {
|
|
77
|
+
clearTimeout(this.id);
|
|
78
|
+
this.id = undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public static expires(durationMS: number): Timeout {
|
|
82
|
+
return new Timeout(undefined, durationMS);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static is(timeout: unknown): timeout is Timeout {
|
|
86
|
+
return (
|
|
87
|
+
typeof timeout === 'object' &&
|
|
88
|
+
timeout != null &&
|
|
89
|
+
Symbol.toStringTag in timeout &&
|
|
90
|
+
timeout[Symbol.toStringTag] === 'MongoDBTimeout' &&
|
|
91
|
+
'then' in timeout &&
|
|
92
|
+
// eslint-disable-next-line github/no-then
|
|
93
|
+
typeof timeout.then === 'function'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|