mongodb 7.0.0 → 7.1.0-dev.20260205.sha.d2ad07f2
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/bson.js +2 -1
- package/lib/bson.js.map +1 -1
- package/lib/change_stream.js +3 -1
- package/lib/change_stream.js.map +1 -1
- package/lib/client-side-encryption/state_machine.js +1 -0
- package/lib/client-side-encryption/state_machine.js.map +1 -1
- package/lib/cmap/auth/aws4.js +161 -0
- package/lib/cmap/auth/aws4.js.map +1 -0
- package/lib/cmap/auth/aws_temporary_credentials.js +1 -0
- package/lib/cmap/auth/aws_temporary_credentials.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +2 -4
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/auth/mongo_credentials.js +2 -1
- package/lib/cmap/auth/mongo_credentials.js.map +1 -1
- package/lib/cmap/auth/mongodb_aws.js +9 -14
- package/lib/cmap/auth/mongodb_aws.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +1 -0
- package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js +1 -0
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -1
- package/lib/cmap/connect.js +15 -0
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +3 -3
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +8 -8
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/cmap/connection_pool_events.js +3 -3
- package/lib/cmap/connection_pool_events.js.map +1 -1
- package/lib/cmap/handshake/client_metadata.js +7 -7
- package/lib/cmap/handshake/client_metadata.js.map +1 -1
- package/lib/cmap/wire_protocol/compression.js +18 -3
- package/lib/cmap/wire_protocol/compression.js.map +1 -1
- package/lib/connection_string.js +6 -0
- package/lib/connection_string.js.map +1 -1
- package/lib/db.js +0 -1
- package/lib/db.js.map +1 -1
- package/lib/deps.js +0 -13
- package/lib/deps.js.map +1 -1
- package/lib/error.js +5 -8
- package/lib/error.js.map +1 -1
- package/lib/gridfs/upload.js +8 -8
- package/lib/gridfs/upload.js.map +1 -1
- package/lib/mongo_client.js +1 -1
- package/lib/mongo_client.js.map +1 -1
- package/lib/mongo_logger.js +11 -5
- package/lib/mongo_logger.js.map +1 -1
- package/lib/operations/execute_operation.js +5 -4
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/rename.js.map +1 -1
- package/lib/runtime_adapters.js +16 -0
- package/lib/runtime_adapters.js.map +1 -0
- package/lib/sdam/monitor.js +8 -8
- package/lib/sdam/monitor.js.map +1 -1
- package/lib/sdam/server.js +41 -29
- package/lib/sdam/server.js.map +1 -1
- package/lib/sdam/server_description.js +1 -1
- package/lib/sdam/server_description.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 +9 -7
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sdam/topology_description.js +1 -1
- package/lib/sdam/topology_description.js.map +1 -1
- package/lib/sessions.js +87 -23
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +21 -47
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +39 -2
- package/package.json +10 -12
- package/src/bson.ts +1 -0
- package/src/change_stream.ts +3 -1
- package/src/client-side-encryption/state_machine.ts +1 -0
- package/src/cmap/auth/aws4.ts +207 -0
- package/src/cmap/auth/aws_temporary_credentials.ts +2 -0
- package/src/cmap/auth/gssapi.ts +8 -4
- package/src/cmap/auth/mongo_credentials.ts +2 -1
- package/src/cmap/auth/mongodb_aws.ts +10 -17
- package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +1 -0
- package/src/cmap/auth/mongodb_oidc/token_machine_workflow.ts +1 -0
- package/src/cmap/connect.ts +19 -0
- package/src/cmap/connection.ts +7 -4
- package/src/cmap/connection_pool.ts +9 -9
- package/src/cmap/connection_pool_events.ts +4 -4
- package/src/cmap/handshake/client_metadata.ts +10 -9
- package/src/cmap/wire_protocol/compression.ts +17 -3
- package/src/connection_string.ts +7 -0
- package/src/db.ts +0 -1
- package/src/deps.ts +0 -60
- package/src/error.ts +4 -8
- package/src/gridfs/upload.ts +8 -8
- package/src/index.ts +2 -1
- package/src/mongo_client.ts +12 -2
- package/src/mongo_logger.ts +11 -6
- package/src/operations/execute_operation.ts +6 -5
- package/src/operations/rename.ts +6 -1
- package/src/runtime_adapters.ts +49 -0
- package/src/sdam/monitor.ts +10 -10
- package/src/sdam/server.ts +50 -32
- package/src/sdam/server_description.ts +8 -2
- package/src/sdam/server_selection.ts +188 -96
- package/src/sdam/topology.ts +20 -16
- package/src/sdam/topology_description.ts +1 -1
- package/src/sessions.ts +114 -30
- package/src/utils.ts +17 -74
package/src/sdam/monitor.ts
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
type EventEmitterWithState,
|
|
15
15
|
makeStateMachine,
|
|
16
16
|
noop,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
ns,
|
|
18
|
+
processTimeMS
|
|
19
19
|
} from '../utils';
|
|
20
20
|
import { ServerType, STATE_CLOSED, STATE_CLOSING } from './common';
|
|
21
21
|
import {
|
|
@@ -326,7 +326,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
326
326
|
);
|
|
327
327
|
// We have not actually sent an outgoing handshake, but when we get the next response we
|
|
328
328
|
// want the duration to reflect the time since we last heard from the server
|
|
329
|
-
start =
|
|
329
|
+
start = processTimeMS();
|
|
330
330
|
} else {
|
|
331
331
|
monitor.rttPinger?.close();
|
|
332
332
|
monitor.rttPinger = undefined;
|
|
@@ -360,7 +360,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
// Record new start time before sending handshake
|
|
363
|
-
start =
|
|
363
|
+
start = processTimeMS();
|
|
364
364
|
|
|
365
365
|
if (isAwaitable) {
|
|
366
366
|
awaited = true;
|
|
@@ -383,7 +383,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
383
383
|
const socket = await makeSocket(monitor.connectOptions);
|
|
384
384
|
const connection = makeConnection(monitor.connectOptions, socket);
|
|
385
385
|
// The start time is after socket creation but before the handshake
|
|
386
|
-
start =
|
|
386
|
+
start = processTimeMS();
|
|
387
387
|
try {
|
|
388
388
|
await performInitialHandshake(connection, monitor.connectOptions);
|
|
389
389
|
return connection;
|
|
@@ -426,7 +426,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
|
|
|
426
426
|
function monitorServer(monitor: Monitor) {
|
|
427
427
|
return (callback: Callback) => {
|
|
428
428
|
if (monitor.s.state === STATE_MONITORING) {
|
|
429
|
-
|
|
429
|
+
queueMicrotask(callback);
|
|
430
430
|
return;
|
|
431
431
|
}
|
|
432
432
|
stateTransition(monitor, STATE_MONITORING);
|
|
@@ -532,7 +532,7 @@ export class RTTPinger {
|
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
private measureRoundTripTime() {
|
|
535
|
-
const start =
|
|
535
|
+
const start = processTimeMS();
|
|
536
536
|
|
|
537
537
|
if (this.closed) {
|
|
538
538
|
return;
|
|
@@ -607,7 +607,7 @@ export class MonitorInterval {
|
|
|
607
607
|
}
|
|
608
608
|
|
|
609
609
|
wake() {
|
|
610
|
-
const currentTime =
|
|
610
|
+
const currentTime = processTimeMS();
|
|
611
611
|
const timeSinceLastCall = currentTime - this.lastExecutionEnded;
|
|
612
612
|
|
|
613
613
|
// TODO(NODE-4674): Add error handling and logging to the monitor
|
|
@@ -651,7 +651,7 @@ export class MonitorInterval {
|
|
|
651
651
|
}
|
|
652
652
|
|
|
653
653
|
toJSON() {
|
|
654
|
-
const currentTime =
|
|
654
|
+
const currentTime = processTimeMS();
|
|
655
655
|
const timeSinceLastCall = currentTime - this.lastExecutionEnded;
|
|
656
656
|
return {
|
|
657
657
|
timerId: this.timerId != null ? 'set' : 'cleared',
|
|
@@ -684,7 +684,7 @@ export class MonitorInterval {
|
|
|
684
684
|
this.isExecutionInProgress = true;
|
|
685
685
|
|
|
686
686
|
this.fn(() => {
|
|
687
|
-
this.lastExecutionEnded =
|
|
687
|
+
this.lastExecutionEnded = processTimeMS();
|
|
688
688
|
this.isExecutionInProgress = false;
|
|
689
689
|
this._reschedule(this.heartbeatFrequencyMS);
|
|
690
690
|
});
|
package/src/sdam/server.ts
CHANGED
|
@@ -22,12 +22,13 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
type AnyError,
|
|
24
24
|
isNodeShuttingDownError,
|
|
25
|
-
|
|
25
|
+
isStateChangeError,
|
|
26
26
|
MONGODB_ERROR_CODES,
|
|
27
27
|
MongoError,
|
|
28
28
|
MongoErrorLabel,
|
|
29
29
|
MongoNetworkError,
|
|
30
30
|
MongoNetworkTimeoutError,
|
|
31
|
+
MongoParseError,
|
|
31
32
|
MongoRuntimeError,
|
|
32
33
|
MongoServerClosedError,
|
|
33
34
|
type MongoServerError,
|
|
@@ -356,7 +357,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
|
|
|
356
357
|
} catch (operationError) {
|
|
357
358
|
if (
|
|
358
359
|
operationError instanceof MongoError &&
|
|
359
|
-
operationError.code === MONGODB_ERROR_CODES.Reauthenticate
|
|
360
|
+
operationError.code?.valueOf() === MONGODB_ERROR_CODES.Reauthenticate
|
|
360
361
|
) {
|
|
361
362
|
reauthPromise = this.pool.reauthenticate(conn);
|
|
362
363
|
reauthPromise.then(undefined, error => {
|
|
@@ -391,9 +392,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
|
|
|
391
392
|
return;
|
|
392
393
|
}
|
|
393
394
|
|
|
394
|
-
|
|
395
|
-
error.connectionGeneration && error.connectionGeneration < this.pool.generation;
|
|
396
|
-
if (isStaleError) {
|
|
395
|
+
if (isStaleError(this, error)) {
|
|
397
396
|
return;
|
|
398
397
|
}
|
|
399
398
|
|
|
@@ -401,33 +400,46 @@ export class Server extends TypedEventEmitter<ServerEvents> {
|
|
|
401
400
|
error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError);
|
|
402
401
|
const isNetworkTimeoutBeforeHandshakeError =
|
|
403
402
|
error instanceof MongoNetworkError && error.beforeHandshake;
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
403
|
+
const isAuthOrEstablishmentHandshakeError = error.hasErrorLabel(MongoErrorLabel.HandshakeError);
|
|
404
|
+
const isSystemOverloadError = error.hasErrorLabel(MongoErrorLabel.SystemOverloadedError);
|
|
405
|
+
|
|
406
|
+
// Perhaps questionable and divergent from the spec, but considering MongoParseErrors like state change errors was legacy behavior.
|
|
407
|
+
if (isStateChangeError(error) || error instanceof MongoParseError) {
|
|
408
|
+
const shouldClearPool = isNodeShuttingDownError(error);
|
|
409
|
+
|
|
410
|
+
// from the SDAM spec: The driver MUST synchronize clearing the pool with updating the topology.
|
|
411
|
+
// In load balanced mode: there is no monitoring, so there is no topology to update. We simply clear the pool.
|
|
412
|
+
// For other topologies: the `ResetPool` label instructs the topology to clear the server's pool in `updateServer()`.
|
|
413
|
+
if (!this.loadBalanced) {
|
|
414
|
+
if (shouldClearPool) {
|
|
415
|
+
error.addErrorLabel(MongoErrorLabel.ResetPool);
|
|
416
|
+
}
|
|
417
|
+
markServerUnknown(this, error);
|
|
418
|
+
queueMicrotask(() => this.requestCheck());
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (connection && shouldClearPool) {
|
|
423
|
+
this.pool.clear({ serviceId: connection.serviceId });
|
|
424
|
+
}
|
|
425
|
+
} else if (
|
|
426
|
+
isNetworkNonTimeoutError ||
|
|
427
|
+
isNetworkTimeoutBeforeHandshakeError ||
|
|
428
|
+
isAuthOrEstablishmentHandshakeError
|
|
429
|
+
) {
|
|
430
|
+
// Do NOT clear the pool if we encounter a system overloaded error.
|
|
431
|
+
if (isSystemOverloadError) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
// from the SDAM spec: The driver MUST synchronize clearing the pool with updating the topology.
|
|
435
|
+
// In load balanced mode: there is no monitoring, so there is no topology to update. We simply clear the pool.
|
|
436
|
+
// For other topologies: the `ResetPool` label instructs the topology to clear the server's pool in `updateServer()`.
|
|
408
437
|
if (!this.loadBalanced) {
|
|
409
438
|
error.addErrorLabel(MongoErrorLabel.ResetPool);
|
|
410
439
|
markServerUnknown(this, error);
|
|
411
440
|
} else if (connection) {
|
|
412
441
|
this.pool.clear({ serviceId: connection.serviceId });
|
|
413
442
|
}
|
|
414
|
-
} else {
|
|
415
|
-
if (isSDAMUnrecoverableError(error)) {
|
|
416
|
-
if (shouldHandleStateChangeError(this, error)) {
|
|
417
|
-
const shouldClearPool = isNodeShuttingDownError(error);
|
|
418
|
-
if (this.loadBalanced && connection && shouldClearPool) {
|
|
419
|
-
this.pool.clear({ serviceId: connection.serviceId });
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
if (!this.loadBalanced) {
|
|
423
|
-
if (shouldClearPool) {
|
|
424
|
-
error.addErrorLabel(MongoErrorLabel.ResetPool);
|
|
425
|
-
}
|
|
426
|
-
markServerUnknown(this, error);
|
|
427
|
-
process.nextTick(() => this.requestCheck());
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
443
|
}
|
|
432
444
|
}
|
|
433
445
|
|
|
@@ -560,12 +572,6 @@ function connectionIsStale(pool: ConnectionPool, connection: Connection) {
|
|
|
560
572
|
return connection.generation !== pool.generation;
|
|
561
573
|
}
|
|
562
574
|
|
|
563
|
-
function shouldHandleStateChangeError(server: Server, err: MongoError) {
|
|
564
|
-
const etv = err.topologyVersion;
|
|
565
|
-
const stv = server.description.topologyVersion;
|
|
566
|
-
return compareTopologyVersion(stv, etv) < 0;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
575
|
function inActiveTransaction(session: ClientSession | undefined, cmd: Document) {
|
|
570
576
|
return session && session.inTransaction() && !isTransactionCommand(cmd);
|
|
571
577
|
}
|
|
@@ -575,3 +581,15 @@ function inActiveTransaction(session: ClientSession | undefined, cmd: Document)
|
|
|
575
581
|
function isRetryableWritesEnabled(topology: Topology) {
|
|
576
582
|
return topology.s.options.retryWrites !== false;
|
|
577
583
|
}
|
|
584
|
+
|
|
585
|
+
function isStaleError(server: Server, error: MongoError): boolean {
|
|
586
|
+
const currentGeneration = server.pool.generation;
|
|
587
|
+
const generation = error.connectionGeneration;
|
|
588
|
+
|
|
589
|
+
if (generation && generation < currentGeneration) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const currentTopologyVersion = server.description.topologyVersion;
|
|
594
|
+
return compareTopologyVersion(currentTopologyVersion, error.topologyVersion) >= 0;
|
|
595
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { type Document, Long, type ObjectId } from '../bson';
|
|
2
2
|
import { type MongoError, MongoRuntimeError } from '../error';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
arrayStrictEqual,
|
|
5
|
+
compareObjectId,
|
|
6
|
+
errorStrictEqual,
|
|
7
|
+
HostAddress,
|
|
8
|
+
processTimeMS
|
|
9
|
+
} from '../utils';
|
|
4
10
|
import { type ClusterTime, ServerType } from './common';
|
|
5
11
|
|
|
6
12
|
const WRITABLE_SERVER_TYPES = new Set<ServerType>([
|
|
@@ -110,7 +116,7 @@ export class ServerDescription {
|
|
|
110
116
|
this.maxWireVersion = hello?.maxWireVersion ?? 0;
|
|
111
117
|
this.roundTripTime = options?.roundTripTime ?? -1;
|
|
112
118
|
this.minRoundTripTime = options?.minRoundTripTime ?? 0;
|
|
113
|
-
this.lastUpdateTime =
|
|
119
|
+
this.lastUpdateTime = processTimeMS();
|
|
114
120
|
this.lastWriteDate = hello?.lastWrite?.lastWriteDate ?? 0;
|
|
115
121
|
// NOTE: This actually builds the stack string instead of holding onto the getter and all its
|
|
116
122
|
// associated references. This is done to prevent a memory leak.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MongoInvalidArgumentError } from '../error';
|
|
1
|
+
import { MongoInvalidArgumentError, MongoRuntimeError } from '../error';
|
|
2
2
|
import { ReadPreference } from '../read_preference';
|
|
3
3
|
import { ServerType, TopologyType } from './common';
|
|
4
4
|
import type { ServerDescription, TagSet } from './server_description';
|
|
@@ -15,21 +15,52 @@ export const MIN_SECONDARY_WRITE_WIRE_VERSION = 13;
|
|
|
15
15
|
export type ServerSelector = (
|
|
16
16
|
topologyDescription: TopologyDescription,
|
|
17
17
|
servers: ServerDescription[],
|
|
18
|
-
deprioritized
|
|
18
|
+
deprioritized: DeprioritizedServers
|
|
19
19
|
) => ServerDescription[];
|
|
20
20
|
|
|
21
|
+
/** @internal */
|
|
22
|
+
export class DeprioritizedServers {
|
|
23
|
+
private deprioritized: Set<string> = new Set();
|
|
24
|
+
|
|
25
|
+
constructor(descriptions?: Iterable<ServerDescription>) {
|
|
26
|
+
for (const description of descriptions ?? []) {
|
|
27
|
+
this.add(description);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
add({ address }: ServerDescription) {
|
|
32
|
+
this.deprioritized.add(address);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
has({ address }: ServerDescription): boolean {
|
|
36
|
+
return this.deprioritized.has(address);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function filterDeprioritized(
|
|
41
|
+
candidates: ServerDescription[],
|
|
42
|
+
deprioritized: DeprioritizedServers
|
|
43
|
+
): ServerDescription[] {
|
|
44
|
+
const filtered = candidates.filter(candidate => !deprioritized.has(candidate));
|
|
45
|
+
|
|
46
|
+
return filtered.length ? filtered : candidates;
|
|
47
|
+
}
|
|
48
|
+
|
|
21
49
|
/**
|
|
22
50
|
* Returns a server selector that selects for writable servers
|
|
23
51
|
*/
|
|
24
52
|
export function writableServerSelector(): ServerSelector {
|
|
25
53
|
return function writableServer(
|
|
26
54
|
topologyDescription: TopologyDescription,
|
|
27
|
-
servers: ServerDescription[]
|
|
55
|
+
servers: ServerDescription[],
|
|
56
|
+
deprioritized: DeprioritizedServers
|
|
28
57
|
): ServerDescription[] {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
58
|
+
const eligibleServers = filterDeprioritized(
|
|
59
|
+
servers.filter(({ isWritable }) => isWritable),
|
|
60
|
+
deprioritized
|
|
32
61
|
);
|
|
62
|
+
|
|
63
|
+
return latencyWindowReducer(topologyDescription, eligibleServers);
|
|
33
64
|
};
|
|
34
65
|
}
|
|
35
66
|
|
|
@@ -39,8 +70,9 @@ export function writableServerSelector(): ServerSelector {
|
|
|
39
70
|
*/
|
|
40
71
|
export function sameServerSelector(description?: ServerDescription): ServerSelector {
|
|
41
72
|
return function sameServerSelector(
|
|
42
|
-
|
|
43
|
-
servers: ServerDescription[]
|
|
73
|
+
_topologyDescription: TopologyDescription,
|
|
74
|
+
servers: ServerDescription[],
|
|
75
|
+
_deprioritized: DeprioritizedServers
|
|
44
76
|
): ServerDescription[] {
|
|
45
77
|
if (!description) return [];
|
|
46
78
|
// Filter the servers to match the provided description only if
|
|
@@ -56,7 +88,7 @@ export function sameServerSelector(description?: ServerDescription): ServerSelec
|
|
|
56
88
|
* server potentially for a write on a secondary.
|
|
57
89
|
*/
|
|
58
90
|
export function secondaryWritableServerSelector(
|
|
59
|
-
wireVersion
|
|
91
|
+
wireVersion: number,
|
|
60
92
|
readPreference?: ReadPreference
|
|
61
93
|
): ServerSelector {
|
|
62
94
|
// If server version < 5.0, read preference always primary.
|
|
@@ -113,7 +145,7 @@ function maxStalenessReducer(
|
|
|
113
145
|
primaryFilter
|
|
114
146
|
)[0];
|
|
115
147
|
|
|
116
|
-
return servers.
|
|
148
|
+
return servers.filter((server: ServerDescription) => {
|
|
117
149
|
const stalenessMS =
|
|
118
150
|
server.lastUpdateTime -
|
|
119
151
|
server.lastWriteDate -
|
|
@@ -122,12 +154,8 @@ function maxStalenessReducer(
|
|
|
122
154
|
|
|
123
155
|
const staleness = stalenessMS / 1000;
|
|
124
156
|
const maxStalenessSeconds = readPreference.maxStalenessSeconds ?? 0;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return result;
|
|
130
|
-
}, []);
|
|
157
|
+
return staleness <= maxStalenessSeconds;
|
|
158
|
+
});
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
if (topologyDescription.type === TopologyType.ReplicaSetNoPrimary) {
|
|
@@ -139,40 +167,38 @@ function maxStalenessReducer(
|
|
|
139
167
|
s.lastWriteDate > max.lastWriteDate ? s : max
|
|
140
168
|
);
|
|
141
169
|
|
|
142
|
-
return servers.
|
|
170
|
+
return servers.filter((server: ServerDescription) => {
|
|
143
171
|
const stalenessMS =
|
|
144
172
|
sMax.lastWriteDate - server.lastWriteDate + topologyDescription.heartbeatFrequencyMS;
|
|
145
173
|
|
|
146
174
|
const staleness = stalenessMS / 1000;
|
|
147
175
|
const maxStalenessSeconds = readPreference.maxStalenessSeconds ?? 0;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return result;
|
|
153
|
-
}, []);
|
|
176
|
+
return staleness <= maxStalenessSeconds;
|
|
177
|
+
});
|
|
154
178
|
}
|
|
155
179
|
|
|
156
180
|
return servers;
|
|
157
181
|
}
|
|
158
182
|
|
|
159
183
|
/**
|
|
160
|
-
* Determines whether a server's tags match a given set of tags
|
|
184
|
+
* Determines whether a server's tags match a given set of tags.
|
|
185
|
+
*
|
|
186
|
+
* A tagset matches the server's tags if every k-v pair in the tagset
|
|
187
|
+
* is also in the server's tagset.
|
|
188
|
+
*
|
|
189
|
+
* Note that this does not requires that every k-v pair in the server's tagset is also
|
|
190
|
+
* in the client's tagset. The server's tagset is required only to be a superset of the
|
|
191
|
+
* client's tags.
|
|
192
|
+
*
|
|
193
|
+
* @see https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.md#tag_sets
|
|
161
194
|
*
|
|
162
195
|
* @param tagSet - The requested tag set to match
|
|
163
196
|
* @param serverTags - The server's tags
|
|
164
197
|
*/
|
|
165
198
|
function tagSetMatch(tagSet: TagSet, serverTags: TagSet) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const key = keys[i];
|
|
170
|
-
if (serverTagKeys.indexOf(key) === -1 || serverTags[key] !== tagSet[key]) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return true;
|
|
199
|
+
return Object.entries(tagSet).every(
|
|
200
|
+
([key, value]) => serverTags[key] != null && serverTags[key] === value
|
|
201
|
+
);
|
|
176
202
|
}
|
|
177
203
|
|
|
178
204
|
/**
|
|
@@ -183,24 +209,17 @@ function tagSetMatch(tagSet: TagSet, serverTags: TagSet) {
|
|
|
183
209
|
* @returns The list of servers matching the requested tags
|
|
184
210
|
*/
|
|
185
211
|
function tagSetReducer(
|
|
186
|
-
|
|
212
|
+
{ tags }: ReadPreference,
|
|
187
213
|
servers: ServerDescription[]
|
|
188
214
|
): ServerDescription[] {
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
(Array.isArray(readPreference.tags) && readPreference.tags.length === 0)
|
|
192
|
-
) {
|
|
215
|
+
if (tags == null || tags.length === 0) {
|
|
216
|
+
// empty tag sets match all servers
|
|
193
217
|
return servers;
|
|
194
218
|
}
|
|
195
219
|
|
|
196
|
-
for (
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
(matched: ServerDescription[], server: ServerDescription) => {
|
|
200
|
-
if (tagSetMatch(tagSet, server.tags)) matched.push(server);
|
|
201
|
-
return matched;
|
|
202
|
-
},
|
|
203
|
-
[]
|
|
220
|
+
for (const tagSet of tags) {
|
|
221
|
+
const serversMatchingTagset = servers.filter((s: ServerDescription) =>
|
|
222
|
+
tagSetMatch(tagSet, s.tags)
|
|
204
223
|
);
|
|
205
224
|
|
|
206
225
|
if (serversMatchingTagset.length) {
|
|
@@ -231,10 +250,7 @@ function latencyWindowReducer(
|
|
|
231
250
|
);
|
|
232
251
|
|
|
233
252
|
const high = low + topologyDescription.localThresholdMS;
|
|
234
|
-
return servers.
|
|
235
|
-
if (server.roundTripTime <= high && server.roundTripTime >= low) result.push(server);
|
|
236
|
-
return result;
|
|
237
|
-
}, []);
|
|
253
|
+
return servers.filter(server => server.roundTripTime <= high && server.roundTripTime >= low);
|
|
238
254
|
}
|
|
239
255
|
|
|
240
256
|
// filters
|
|
@@ -258,6 +274,107 @@ function loadBalancerFilter(server: ServerDescription): boolean {
|
|
|
258
274
|
return server.type === ServerType.LoadBalancer;
|
|
259
275
|
}
|
|
260
276
|
|
|
277
|
+
function isDeprioritizedFactory(
|
|
278
|
+
deprioritized: DeprioritizedServers
|
|
279
|
+
): (server: ServerDescription) => boolean {
|
|
280
|
+
return server =>
|
|
281
|
+
// if any deprioritized servers equal the server, here we are.
|
|
282
|
+
!deprioritized.has(server);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function secondarySelector(
|
|
286
|
+
readPreference: ReadPreference,
|
|
287
|
+
topologyDescription: TopologyDescription,
|
|
288
|
+
servers: ServerDescription[],
|
|
289
|
+
deprioritized: DeprioritizedServers
|
|
290
|
+
) {
|
|
291
|
+
const mode = readPreference.mode;
|
|
292
|
+
switch (mode) {
|
|
293
|
+
case 'primary':
|
|
294
|
+
// Note: no need to filter for deprioritized servers. A replica set has only one primary; that means that
|
|
295
|
+
// we are in one of two scenarios:
|
|
296
|
+
// 1. deprioritized servers is empty - return the primary.
|
|
297
|
+
// 2. deprioritized servers contains the primary - return the primary.
|
|
298
|
+
return servers.filter(primaryFilter);
|
|
299
|
+
case 'primaryPreferred': {
|
|
300
|
+
const primary = servers.filter(primaryFilter);
|
|
301
|
+
|
|
302
|
+
// If there is a primary and it is not deprioritized, use the primary. Otherwise,
|
|
303
|
+
// check for secondaries.
|
|
304
|
+
const eligiblePrimary = primary.filter(isDeprioritizedFactory(deprioritized));
|
|
305
|
+
if (eligiblePrimary.length) {
|
|
306
|
+
return eligiblePrimary;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// If we make it here, we either have:
|
|
310
|
+
// 1. a deprioritized primary
|
|
311
|
+
// 2. no eligible primary
|
|
312
|
+
// secondaries take precedence of deprioritized primaries.
|
|
313
|
+
const secondaries = tagSetReducer(
|
|
314
|
+
readPreference,
|
|
315
|
+
maxStalenessReducer(readPreference, topologyDescription, servers.filter(secondaryFilter))
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const eligibleSecondaries = secondaries.filter(isDeprioritizedFactory(deprioritized));
|
|
319
|
+
if (eligibleSecondaries.length) {
|
|
320
|
+
return latencyWindowReducer(topologyDescription, eligibleSecondaries);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// if we make it here, we have no primaries or secondaries that not deprioritized.
|
|
324
|
+
// prefer the primary (which may not exist, if the topology has no primary).
|
|
325
|
+
// otherwise, return the secondaries (which also may not exist, but there is nothing else to check here).
|
|
326
|
+
return primary.length ? primary : latencyWindowReducer(topologyDescription, secondaries);
|
|
327
|
+
}
|
|
328
|
+
case 'nearest': {
|
|
329
|
+
const eligible = filterDeprioritized(
|
|
330
|
+
tagSetReducer(
|
|
331
|
+
readPreference,
|
|
332
|
+
maxStalenessReducer(readPreference, topologyDescription, servers.filter(nearestFilter))
|
|
333
|
+
),
|
|
334
|
+
deprioritized
|
|
335
|
+
);
|
|
336
|
+
return latencyWindowReducer(topologyDescription, eligible);
|
|
337
|
+
}
|
|
338
|
+
case 'secondary':
|
|
339
|
+
case 'secondaryPreferred': {
|
|
340
|
+
const secondaries = tagSetReducer(
|
|
341
|
+
readPreference,
|
|
342
|
+
maxStalenessReducer(readPreference, topologyDescription, servers.filter(secondaryFilter))
|
|
343
|
+
);
|
|
344
|
+
const eligibleSecondaries = secondaries.filter(isDeprioritizedFactory(deprioritized));
|
|
345
|
+
|
|
346
|
+
if (eligibleSecondaries.length) {
|
|
347
|
+
return latencyWindowReducer(topologyDescription, eligibleSecondaries);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// we have no eligible secondaries, try for a primary if we can.
|
|
351
|
+
if (mode === ReadPreference.SECONDARY_PREFERRED) {
|
|
352
|
+
const primary = servers.filter(primaryFilter);
|
|
353
|
+
|
|
354
|
+
// unlike readPreference=primary, here we do filter for deprioritized servers.
|
|
355
|
+
// if the primary is deprioritized, deprioritized secondaries take precedence.
|
|
356
|
+
const eligiblePrimary = primary.filter(isDeprioritizedFactory(deprioritized));
|
|
357
|
+
if (eligiblePrimary.length) return eligiblePrimary;
|
|
358
|
+
|
|
359
|
+
// we have no eligible primary nor secondaries that have not been deprioritized
|
|
360
|
+
return secondaries.length
|
|
361
|
+
? latencyWindowReducer(topologyDescription, secondaries)
|
|
362
|
+
: primary;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// return all secondaries in the latency window.
|
|
366
|
+
return latencyWindowReducer(topologyDescription, secondaries);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
default: {
|
|
370
|
+
const _exhaustiveCheck: never = mode;
|
|
371
|
+
throw new MongoRuntimeError(
|
|
372
|
+
`unexpected readPreference=${mode} (should never happen). Please report a bug in the Node driver Jira project.`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
261
378
|
/**
|
|
262
379
|
* Returns a function which selects servers based on a provided read preference
|
|
263
380
|
*
|
|
@@ -271,53 +388,28 @@ export function readPreferenceServerSelector(readPreference: ReadPreference): Se
|
|
|
271
388
|
return function readPreferenceServers(
|
|
272
389
|
topologyDescription: TopologyDescription,
|
|
273
390
|
servers: ServerDescription[],
|
|
274
|
-
deprioritized:
|
|
391
|
+
deprioritized: DeprioritizedServers
|
|
275
392
|
): ServerDescription[] {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const mode = readPreference.mode;
|
|
297
|
-
if (mode === ReadPreference.PRIMARY) {
|
|
298
|
-
return servers.filter(primaryFilter);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (mode === ReadPreference.PRIMARY_PREFERRED) {
|
|
302
|
-
const result = servers.filter(primaryFilter);
|
|
303
|
-
if (result.length) {
|
|
304
|
-
return result;
|
|
393
|
+
switch (topologyDescription.type) {
|
|
394
|
+
case 'Single':
|
|
395
|
+
return latencyWindowReducer(topologyDescription, servers.filter(knownFilter));
|
|
396
|
+
case 'ReplicaSetNoPrimary':
|
|
397
|
+
case 'ReplicaSetWithPrimary':
|
|
398
|
+
return secondarySelector(readPreference, topologyDescription, servers, deprioritized);
|
|
399
|
+
case 'Sharded': {
|
|
400
|
+
const selectable = filterDeprioritized(servers, deprioritized);
|
|
401
|
+
return latencyWindowReducer(topologyDescription, selectable.filter(knownFilter));
|
|
402
|
+
}
|
|
403
|
+
case 'Unknown':
|
|
404
|
+
return [];
|
|
405
|
+
case 'LoadBalanced':
|
|
406
|
+
return servers.filter(loadBalancerFilter);
|
|
407
|
+
default: {
|
|
408
|
+
const _exhaustiveCheck: never = topologyDescription.type;
|
|
409
|
+
throw new MongoRuntimeError(
|
|
410
|
+
`unexpected topology type: ${topologyDescription.type} (this should never happen). Please file a bug in the Node driver Jira project.`
|
|
411
|
+
);
|
|
305
412
|
}
|
|
306
413
|
}
|
|
307
|
-
|
|
308
|
-
const filter = mode === ReadPreference.NEAREST ? nearestFilter : secondaryFilter;
|
|
309
|
-
const selectedServers = latencyWindowReducer(
|
|
310
|
-
topologyDescription,
|
|
311
|
-
tagSetReducer(
|
|
312
|
-
readPreference,
|
|
313
|
-
maxStalenessReducer(readPreference, topologyDescription, servers.filter(filter))
|
|
314
|
-
)
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
if (mode === ReadPreference.SECONDARY_PREFERRED && selectedServers.length === 0) {
|
|
318
|
-
return servers.filter(primaryFilter);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return selectedServers;
|
|
322
414
|
};
|
|
323
415
|
}
|