Skip to content

Commit

Permalink
fix(NODE-3071): Ignore error message if error code is defined (#2770)
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 Apr 6, 2021
1 parent 341a602 commit d4cc936
Show file tree
Hide file tree
Showing 82 changed files with 4,755 additions and 192 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
2 changes: 1 addition & 1 deletion src/cmap/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type WriteProtocolMessageType = Query | Msg | GetMore | KillCursor;

/** @internal */
export interface OpQueryOptions extends CommandOptions {
socketTimeout?: number;
socketTimeoutMS?: number;
session?: ClientSession;
documentsReturnedIn?: string;
numberToSkip?: number;
Expand Down
10 changes: 5 additions & 5 deletions src/cmap/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function performInitialHandshake(
const handshakeOptions: Document = Object.assign({}, options);
if (typeof options.connectTimeoutMS === 'number') {
// The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
handshakeOptions.socketTimeout = options.connectTimeoutMS;
handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;
}

const start = new Date().getTime();
Expand Down Expand Up @@ -262,13 +262,13 @@ const SOCKET_ERROR_EVENTS = new Set(SOCKET_ERROR_EVENT_LIST);
function makeConnection(options: ConnectionOptions, _callback: CallbackWithType<AnyError, Stream>) {
const useTLS = options.tls ?? false;
const keepAlive = options.keepAlive ?? true;
const socketTimeout = options.socketTimeout ?? 0;
const socketTimeoutMS = options.socketTimeoutMS ?? Reflect.get(options, 'socketTimeout') ?? 0;
const noDelay = options.noDelay ?? true;
const connectionTimeout = options.connectTimeoutMS ?? 30000;
const rejectUnauthorized = options.rejectUnauthorized ?? true;
const keepAliveInitialDelay =
((options.keepAliveInitialDelay ?? 120000) > socketTimeout
? Math.round(socketTimeout / 2)
((options.keepAliveInitialDelay ?? 120000) > socketTimeoutMS
? Math.round(socketTimeoutMS / 2)
: options.keepAliveInitialDelay) ?? 120000;

let socket: Stream;
Expand Down Expand Up @@ -320,7 +320,7 @@ function makeConnection(options: ConnectionOptions, _callback: CallbackWithType<
}
}

socket.setTimeout(socketTimeout);
socket.setTimeout(socketTimeoutMS);
callback(undefined, socket);
}

Expand Down
14 changes: 7 additions & 7 deletions src/cmap/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export interface CommandOptions extends BSONSerializeOptions {
raw?: boolean;
monitoring?: boolean;
fullResult?: boolean;
socketTimeout?: number;
socketTimeoutMS?: number;
/** Session to use for the operation */
session?: ClientSession;
documentsReturnedIn?: string;
Expand Down Expand Up @@ -120,7 +120,7 @@ export interface ConnectionOptions
keepAlive?: boolean;
keepAliveInitialDelay?: number;
noDelay?: boolean;
socketTimeout?: number;
socketTimeoutMS?: number;
cancellationToken?: EventEmitter;

metadata: ClientMetadata;
Expand All @@ -136,7 +136,7 @@ export interface DestroyOptions {
export class Connection extends EventEmitter {
id: number | '<monitor>';
address: string;
socketTimeout: number;
socketTimeoutMS: number;
monitorCommands: boolean;
closed: boolean;
destroyed: boolean;
Expand Down Expand Up @@ -172,7 +172,7 @@ export class Connection extends EventEmitter {
super();
this.id = options.id;
this.address = streamIdentifier(stream);
this.socketTimeout = options.socketTimeout ?? 0;
this.socketTimeoutMS = options.socketTimeoutMS ?? 0;
this.monitorCommands = options.monitorCommands;
this.serverApi = options.serverApi;
this.closed = false;
Expand Down Expand Up @@ -674,7 +674,7 @@ function messageHandler(conn: Connection) {
// requeue the callback for next synthetic request
conn[kQueue].set(message.requestId, operationDescription);
} else if (operationDescription.socketTimeoutOverride) {
conn[kStream].setTimeout(conn.socketTimeout);
conn[kStream].setTimeout(conn.socketTimeoutMS);
}

try {
Expand Down Expand Up @@ -764,9 +764,9 @@ function write(
}
}

if (typeof options.socketTimeout === 'number') {
if (typeof options.socketTimeoutMS === 'number') {
operationDescription.socketTimeoutOverride = true;
conn[kStream].setTimeout(options.socketTimeout);
conn[kStream].setTimeout(options.socketTimeoutMS);
}

// if command monitoring is enabled we need to modify the callback here
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
Loading

0 comments on commit d4cc936

Please sign in to comment.