Skip to content

Commit

Permalink
fix(NODE-3071): Ignore error message if error code is defined
Browse files Browse the repository at this point in the history
Prior behavior was to check the error message if the code did not
correspond to the server state. With this change the driver will not
inspect the error message for error type. ServerDescriptions with
topology versions less than the current will be ignored.

NODE-3071, NODE-2559
  • Loading branch information
nbbeeken committed Mar 26, 2021
1 parent a928594 commit eb21117
Show file tree
Hide file tree
Showing 74 changed files with 4,849 additions and 161 deletions.
9 changes: 4 additions & 5 deletions src/bulk/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PromiseProvider } from '../promise_provider';
import { Long, ObjectId, Document, BSONSerializeOptions, resolveBSONOptions } from '../bson';
import { MongoError, MongoWriteConcernError, AnyError } from '../error';
import { MongoError, MongoWriteConcernError, AnyError, MONGODB_ERROR_CODES } from '../error';
import {
applyRetryableWrites,
executeLegacyOperation,
Expand All @@ -20,9 +20,6 @@ import type { Topology } from '../sdam/topology';
import type { CommandOperationOptions, CollationOptions } from '../operations/command';
import type { Hint } from '../operations/operation';

// Error codes
const WRITE_CONCERN_ERROR = 64;

/** @public */
export const BatchType = {
INSERT: 1,
Expand Down Expand Up @@ -307,7 +304,9 @@ export class BulkWriteResult {
if (i === 0) errmsg = errmsg + ' and ';
}

return new WriteConcernError(new MongoError({ errmsg: errmsg, code: WRITE_CONCERN_ERROR }));
return new WriteConcernError(
new MongoError({ errmsg: errmsg, code: MONGODB_ERROR_CODES.WriteConcernFailed })
);
}
}

Expand Down
159 changes: 97 additions & 62 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,55 @@ export type AnyError = MongoError | Error;

const kErrorLabels = Symbol('errorLabels');

/** @internal MongoDB Error Codes */
export const MONGODB_ERROR_CODES = Object.freeze({
HostUnreachable: 6,
HostNotFound: 7,
NetworkTimeout: 89,
ShutdownInProgress: 91,
PrimarySteppedDown: 189,
ExceededTimeLimit: 262,
SocketException: 9001,
NotMaster: 10107,
InterruptedAtShutdown: 11600,
InterruptedDueToReplStateChange: 11602,
NotMasterNoSlaveOk: 13435,
NotMasterOrSecondary: 13436,
StaleShardVersion: 63,
StaleEpoch: 150,
StaleConfig: 13388,
RetryChangeStream: 234,
FailedToSatisfyReadPreference: 133,
CursorNotFound: 43,
LegacyNotPrimary: 10058,
WriteConcernFailed: 64,
NamespaceNotFound: 26,
IllegalOperation: 20,
MaxTimeMSExpired: 50,
UnknownReplWriteConcern: 79,
UnsatisfiableWriteConcern: 100
} as const);

// From spec@https://github.com/mongodb/specifications/blob/f93d78191f3db2898a59013a7ed5650352ef6da8/source/change-streams/change-streams.rst#resumable-error
export const GET_MORE_RESUMABLE_CODES = new Set([
6, // HostUnreachable
7, // HostNotFound
89, // NetworkTimeout
91, // ShutdownInProgress
189, // PrimarySteppedDown
262, // ExceededTimeLimit
9001, // SocketException
10107, // NotMaster
11600, // InterruptedAtShutdown
11602, // InterruptedDueToReplStateChange
13435, // NotMasterNoSlaveOk
13436, // NotMasterOrSecondary
63, // StaleShardVersion
150, // StaleEpoch
13388, // StaleConfig
234, // RetryChangeStream
133, // FailedToSatisfyReadPreference
43 // CursorNotFound
export const GET_MORE_RESUMABLE_CODES = new Set<number>([
MONGODB_ERROR_CODES.HostUnreachable,
MONGODB_ERROR_CODES.HostNotFound,
MONGODB_ERROR_CODES.NetworkTimeout,
MONGODB_ERROR_CODES.ShutdownInProgress,
MONGODB_ERROR_CODES.PrimarySteppedDown,
MONGODB_ERROR_CODES.ExceededTimeLimit,
MONGODB_ERROR_CODES.SocketException,
MONGODB_ERROR_CODES.NotMaster,
MONGODB_ERROR_CODES.InterruptedAtShutdown,
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
MONGODB_ERROR_CODES.NotMasterOrSecondary,
MONGODB_ERROR_CODES.StaleShardVersion,
MONGODB_ERROR_CODES.StaleEpoch,
MONGODB_ERROR_CODES.StaleConfig,
MONGODB_ERROR_CODES.RetryChangeStream,
MONGODB_ERROR_CODES.FailedToSatisfyReadPreference,
MONGODB_ERROR_CODES.CursorNotFound
]);

/** @public */
Expand Down Expand Up @@ -244,33 +273,33 @@ export class MongoWriteConcernError extends MongoError {
}

// see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
const RETRYABLE_ERROR_CODES = new Set([
6, // HostUnreachable
7, // HostNotFound
89, // NetworkTimeout
91, // ShutdownInProgress
189, // PrimarySteppedDown
9001, // SocketException
10107, // NotMaster
11600, // InterruptedAtShutdown
11602, // InterruptedDueToReplStateChange
13435, // NotMasterNoSlaveOk
13436 // NotMasterOrSecondary
const RETRYABLE_ERROR_CODES = new Set<number>([
MONGODB_ERROR_CODES.HostUnreachable,
MONGODB_ERROR_CODES.HostNotFound,
MONGODB_ERROR_CODES.NetworkTimeout,
MONGODB_ERROR_CODES.ShutdownInProgress,
MONGODB_ERROR_CODES.PrimarySteppedDown,
MONGODB_ERROR_CODES.SocketException,
MONGODB_ERROR_CODES.NotMaster,
MONGODB_ERROR_CODES.InterruptedAtShutdown,
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
MONGODB_ERROR_CODES.NotMasterOrSecondary
]);

const RETRYABLE_WRITE_ERROR_CODES = new Set([
11600, // InterruptedAtShutdown
11602, // InterruptedDueToReplStateChange
10107, // NotMaster
13435, // NotMasterNoSlaveOk
13436, // NotMasterOrSecondary
189, // PrimarySteppedDown
91, // ShutdownInProgress
7, // HostNotFound
6, // HostUnreachable
89, // NetworkTimeout
9001, // SocketException
262 // ExceededTimeLimit
const RETRYABLE_WRITE_ERROR_CODES = new Set<number>([
MONGODB_ERROR_CODES.InterruptedAtShutdown,
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
MONGODB_ERROR_CODES.NotMaster,
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
MONGODB_ERROR_CODES.NotMasterOrSecondary,
MONGODB_ERROR_CODES.PrimarySteppedDown,
MONGODB_ERROR_CODES.ShutdownInProgress,
MONGODB_ERROR_CODES.HostNotFound,
MONGODB_ERROR_CODES.HostUnreachable,
MONGODB_ERROR_CODES.NetworkTimeout,
MONGODB_ERROR_CODES.SocketException,
MONGODB_ERROR_CODES.ExceededTimeLimit
]);

export function isRetryableWriteError(error: MongoError): boolean {
Expand All @@ -291,42 +320,45 @@ export function isRetryableError(error: MongoError): boolean {
);
}

const SDAM_RECOVERING_CODES = new Set([
91, // ShutdownInProgress
189, // PrimarySteppedDown
11600, // InterruptedAtShutdown
11602, // InterruptedDueToReplStateChange
13436 // NotMasterOrSecondary
const SDAM_RECOVERING_CODES = new Set<number>([
MONGODB_ERROR_CODES.ShutdownInProgress,
MONGODB_ERROR_CODES.PrimarySteppedDown,
MONGODB_ERROR_CODES.InterruptedAtShutdown,
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
MONGODB_ERROR_CODES.NotMasterOrSecondary
]);

const SDAM_NOTMASTER_CODES = new Set([
10107, // NotMaster
13435 // NotMasterNoSlaveOk
const SDAM_NOTMASTER_CODES = new Set<number>([
MONGODB_ERROR_CODES.NotMaster,
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
MONGODB_ERROR_CODES.LegacyNotPrimary
]);

const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
11600, // InterruptedAtShutdown
91 // ShutdownInProgress
const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set<number>([
MONGODB_ERROR_CODES.InterruptedAtShutdown,
MONGODB_ERROR_CODES.ShutdownInProgress
]);

function isRecoveringError(err: MongoError) {
if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
return true;
if (typeof err.code !== 'undefined') {
// If any error code exists, we ignore the error.message
return SDAM_RECOVERING_CODES.has(err.code);
}

return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
return /not master or secondary/.test(err.message) || /node is recovering/.test(err.message);
}

function isNotMasterError(err: MongoError) {
if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
return true;
if (typeof err.code !== 'undefined') {
// If any error code exists, we ignore the error.message
return SDAM_NOTMASTER_CODES.has(err.code);
}

if (isRecoveringError(err)) {
return false;
}

return err.message.match(/not master/);
return /not master/.test(err.message);
}

export function isNodeShuttingDownError(err: MongoError): boolean {
Expand All @@ -347,6 +379,9 @@ export function isSDAMUnrecoverableError(error: MongoError): boolean {
return true;
}

if (typeof error.code !== 'undefined') {
return isRecoveringError(error) || isNotMasterError(error);
}
if (isRecoveringError(error) || isNotMasterError(error)) {
return true;
}
Expand Down
8 changes: 3 additions & 5 deletions src/gridfs-stream/upload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as crypto from 'crypto';
import { Writable } from 'stream';
import { MongoError, AnyError } from '../error';
import { MongoError, AnyError, MONGODB_ERROR_CODES } from '../error';
import { WriteConcern } from './../write_concern';
import { PromiseProvider } from '../promise_provider';
import { ObjectId } from '../bson';
Expand All @@ -11,8 +11,6 @@ import type { GridFSBucket } from './index';
import type { GridFSFile } from './download';
import type { WriteConcernOptions } from '../write_concern';

const ERROR_NAMESPACE_NOT_FOUND = 26;

/** @public */
export type TFileId = string | number | Document | ObjectId;

Expand Down Expand Up @@ -256,7 +254,7 @@ function checkChunksIndex(stream: GridFSBucketWriteStream, callback: Callback):
let index: { files_id: number; n: number };
if (error) {
// Collection doesn't exist so create index
if (error instanceof MongoError && error.code === ERROR_NAMESPACE_NOT_FOUND) {
if (error instanceof MongoError && error.code === MONGODB_ERROR_CODES.NamespaceNotFound) {
index = { files_id: 1, n: 1 };
stream.chunks.createIndex(index, { background: false, unique: true }, error => {
if (error) {
Expand Down Expand Up @@ -349,7 +347,7 @@ function checkIndexes(stream: GridFSBucketWriteStream, callback: Callback): void
let index: { filename: number; uploadDate: number };
if (error) {
// Collection doesn't exist so create index
if (error instanceof MongoError && error.code === ERROR_NAMESPACE_NOT_FOUND) {
if (error instanceof MongoError && error.code === MONGODB_ERROR_CODES.NamespaceNotFound) {
index = { filename: 1, uploadDate: 1 };
stream.files.createIndex(index, { background: false }, (error?: AnyError) => {
if (error) {
Expand Down
4 changes: 2 additions & 2 deletions src/operations/execute_operation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReadPreference } from '../read_preference';
import { MongoError, isRetryableError } from '../error';
import { MongoError, isRetryableError, MONGODB_ERROR_CODES } from '../error';
import { Aspect, AbstractOperation } from './operation';
import { maxWireVersion, maybePromise, Callback } from '../utils';
import { ServerType } from '../sdam/common';
Expand All @@ -8,7 +8,7 @@ import type { Topology } from '../sdam/topology';
import type { ClientSession } from '../sessions';
import type { Document } from '../bson';

const MMAPv1_RETRY_WRITES_ERROR_CODE = 20;
const MMAPv1_RETRY_WRITES_ERROR_CODE = MONGODB_ERROR_CODES.IllegalOperation;
const MMAPv1_RETRY_WRITES_ERROR_MESSAGE =
'This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.';

Expand Down
4 changes: 2 additions & 2 deletions src/operations/indexes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { indexInformation, IndexInformationOptions } from './common_functions';
import { AbstractOperation, Aspect, defineAspects } from './operation';
import { MongoError } from '../error';
import { MONGODB_ERROR_CODES, MongoError } from '../error';
import {
maxWireVersion,
parseIndexOptions,
Expand Down Expand Up @@ -280,7 +280,7 @@ export class EnsureIndexOperation extends CreateIndexOperation {
const cursor = this.db.collection(this.collectionName).listIndexes({ session });
cursor.toArray((err, indexes) => {
/// ignore "NamespaceNotFound" errors
if (err && (err as MongoError).code !== 26) {
if (err && (err as MongoError).code !== MONGODB_ERROR_CODES.NamespaceNotFound) {
return callback(err);
}

Expand Down
6 changes: 2 additions & 4 deletions src/sdam/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,8 @@ function monitorServer(monitor: Monitor) {
function makeTopologyVersion(tv: TopologyVersion) {
return {
processId: tv.processId,

// NOTE: The casting here is a bug, `counter` should always be a `Long`
// but it was not at the time of typing. Further investigation needed
counter: Long.fromNumber((tv.counter as unknown) as number)
// tests mock counter as just number, but in a real situation counter should always be a Long
counter: Long.isLong(tv.counter) ? tv.counter : Long.fromNumber(tv.counter)
};
}

Expand Down
10 changes: 6 additions & 4 deletions src/sdam/server_description.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { arrayStrictEqual, errorStrictEqual, now, HostAddress } from '../utils';
import { ServerType, ServerTypeId } from './common';
import type { ObjectId, Long, Document } from '../bson';
import { ObjectId, Long, Document } from '../bson';
import type { ClusterTime } from './common';

const WRITABLE_SERVER_TYPES = new Set<ServerTypeId>([
Expand Down Expand Up @@ -256,10 +256,12 @@ export function compareTopologyVersion(lhs?: TopologyVersion, rhs?: TopologyVers
}

if (lhs.processId.equals(rhs.processId)) {
// TODO: handle counters as Longs
if (lhs.counter === rhs.counter) {
// tests mock counter as just number, but in a real situation counter should always be a Long
const lhsCounter = Long.isLong(lhs.counter) ? lhs.counter : Long.fromNumber(lhs.counter);
const rhsCounter = Long.isLong(rhs.counter) ? lhs.counter : Long.fromNumber(rhs.counter);
if (lhsCounter.equals(rhsCounter)) {
return 0;
} else if (lhs.counter < rhs.counter) {
} else if (lhsCounter.lessThan(rhsCounter)) {
return -1;
}

Expand Down
20 changes: 19 additions & 1 deletion src/sdam/topology.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Denque = require('denque');
import { EventEmitter } from 'events';
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
import { ServerDescription } from './server_description';
import { compareTopologyVersion, ServerDescription } from './server_description';
import { TopologyDescription } from './topology_description';
import { Server, ServerOptions } from './server';
import {
Expand Down Expand Up @@ -612,6 +612,11 @@ export class Topology extends EventEmitter {
return;
}

// ignore this server update if its from an outdated topologyVersion
if (isStaleServerDescription(this.s.description, serverDescription)) {
return;
}

// these will be used for monitoring events later
const previousTopologyDescription = this.s.description;
const previousServerDescription = this.s.description.servers.get(serverDescription.address);
Expand Down Expand Up @@ -965,6 +970,19 @@ function processWaitQueue(topology: Topology) {
}
}

function isStaleServerDescription(
topologyDescription: TopologyDescription,
incomingServerDescription: ServerDescription
) {
const currentServerDescription = topologyDescription.servers.get(
incomingServerDescription.address
);
const currentTopologyVersion = currentServerDescription?.topologyVersion;
return (
compareTopologyVersion(currentTopologyVersion, incomingServerDescription.topologyVersion) > 0
);
}

/** @public */
export class ServerCapabilities {
maxWireVersion: number;
Expand Down
Loading

0 comments on commit eb21117

Please sign in to comment.