Skip to content

Commit d4cc936

Browse files
authored
fix(NODE-3071): Ignore error message if error code is defined (#2770)
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
1 parent 341a602 commit d4cc936

File tree

82 files changed

+4755
-192
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+4755
-192
lines changed

src/bulk/common.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PromiseProvider } from '../promise_provider';
22
import { Long, ObjectId, Document, BSONSerializeOptions, resolveBSONOptions } from '../bson';
3-
import { MongoError, MongoWriteConcernError, AnyError } from '../error';
3+
import { MongoError, MongoWriteConcernError, AnyError, MONGODB_ERROR_CODES } from '../error';
44
import {
55
applyRetryableWrites,
66
executeLegacyOperation,
@@ -20,9 +20,6 @@ import type { Topology } from '../sdam/topology';
2020
import type { CommandOperationOptions, CollationOptions } from '../operations/command';
2121
import type { Hint } from '../operations/operation';
2222

23-
// Error codes
24-
const WRITE_CONCERN_ERROR = 64;
25-
2623
/** @public */
2724
export const BatchType = {
2825
INSERT: 1,
@@ -307,7 +304,9 @@ export class BulkWriteResult {
307304
if (i === 0) errmsg = errmsg + ' and ';
308305
}
309306

310-
return new WriteConcernError(new MongoError({ errmsg: errmsg, code: WRITE_CONCERN_ERROR }));
307+
return new WriteConcernError(
308+
new MongoError({ errmsg: errmsg, code: MONGODB_ERROR_CODES.WriteConcernFailed })
309+
);
311310
}
312311
}
313312

src/cmap/commands.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export type WriteProtocolMessageType = Query | Msg | GetMore | KillCursor;
3030

3131
/** @internal */
3232
export interface OpQueryOptions extends CommandOptions {
33-
socketTimeout?: number;
33+
socketTimeoutMS?: number;
3434
session?: ClientSession;
3535
documentsReturnedIn?: string;
3636
numberToSkip?: number;

src/cmap/connect.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function performInitialHandshake(
9393
const handshakeOptions: Document = Object.assign({}, options);
9494
if (typeof options.connectTimeoutMS === 'number') {
9595
// The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
96-
handshakeOptions.socketTimeout = options.connectTimeoutMS;
96+
handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;
9797
}
9898

9999
const start = new Date().getTime();
@@ -262,13 +262,13 @@ const SOCKET_ERROR_EVENTS = new Set(SOCKET_ERROR_EVENT_LIST);
262262
function makeConnection(options: ConnectionOptions, _callback: CallbackWithType<AnyError, Stream>) {
263263
const useTLS = options.tls ?? false;
264264
const keepAlive = options.keepAlive ?? true;
265-
const socketTimeout = options.socketTimeout ?? 0;
265+
const socketTimeoutMS = options.socketTimeoutMS ?? Reflect.get(options, 'socketTimeout') ?? 0;
266266
const noDelay = options.noDelay ?? true;
267267
const connectionTimeout = options.connectTimeoutMS ?? 30000;
268268
const rejectUnauthorized = options.rejectUnauthorized ?? true;
269269
const keepAliveInitialDelay =
270-
((options.keepAliveInitialDelay ?? 120000) > socketTimeout
271-
? Math.round(socketTimeout / 2)
270+
((options.keepAliveInitialDelay ?? 120000) > socketTimeoutMS
271+
? Math.round(socketTimeoutMS / 2)
272272
: options.keepAliveInitialDelay) ?? 120000;
273273

274274
let socket: Stream;
@@ -320,7 +320,7 @@ function makeConnection(options: ConnectionOptions, _callback: CallbackWithType<
320320
}
321321
}
322322

323-
socket.setTimeout(socketTimeout);
323+
socket.setTimeout(socketTimeoutMS);
324324
callback(undefined, socket);
325325
}
326326

src/cmap/connection.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export interface CommandOptions extends BSONSerializeOptions {
8080
raw?: boolean;
8181
monitoring?: boolean;
8282
fullResult?: boolean;
83-
socketTimeout?: number;
83+
socketTimeoutMS?: number;
8484
/** Session to use for the operation */
8585
session?: ClientSession;
8686
documentsReturnedIn?: string;
@@ -120,7 +120,7 @@ export interface ConnectionOptions
120120
keepAlive?: boolean;
121121
keepAliveInitialDelay?: number;
122122
noDelay?: boolean;
123-
socketTimeout?: number;
123+
socketTimeoutMS?: number;
124124
cancellationToken?: EventEmitter;
125125

126126
metadata: ClientMetadata;
@@ -136,7 +136,7 @@ export interface DestroyOptions {
136136
export class Connection extends EventEmitter {
137137
id: number | '<monitor>';
138138
address: string;
139-
socketTimeout: number;
139+
socketTimeoutMS: number;
140140
monitorCommands: boolean;
141141
closed: boolean;
142142
destroyed: boolean;
@@ -172,7 +172,7 @@ export class Connection extends EventEmitter {
172172
super();
173173
this.id = options.id;
174174
this.address = streamIdentifier(stream);
175-
this.socketTimeout = options.socketTimeout ?? 0;
175+
this.socketTimeoutMS = options.socketTimeoutMS ?? 0;
176176
this.monitorCommands = options.monitorCommands;
177177
this.serverApi = options.serverApi;
178178
this.closed = false;
@@ -674,7 +674,7 @@ function messageHandler(conn: Connection) {
674674
// requeue the callback for next synthetic request
675675
conn[kQueue].set(message.requestId, operationDescription);
676676
} else if (operationDescription.socketTimeoutOverride) {
677-
conn[kStream].setTimeout(conn.socketTimeout);
677+
conn[kStream].setTimeout(conn.socketTimeoutMS);
678678
}
679679

680680
try {
@@ -764,9 +764,9 @@ function write(
764764
}
765765
}
766766

767-
if (typeof options.socketTimeout === 'number') {
767+
if (typeof options.socketTimeoutMS === 'number') {
768768
operationDescription.socketTimeoutOverride = true;
769-
conn[kStream].setTimeout(options.socketTimeout);
769+
conn[kStream].setTimeout(options.socketTimeoutMS);
770770
}
771771

772772
// if command monitoring is enabled we need to modify the callback here

src/error.ts

+97-62
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,55 @@ export type AnyError = MongoError | Error;
77

88
const kErrorLabels = Symbol('errorLabels');
99

10+
/** @internal MongoDB Error Codes */
11+
export const MONGODB_ERROR_CODES = Object.freeze({
12+
HostUnreachable: 6,
13+
HostNotFound: 7,
14+
NetworkTimeout: 89,
15+
ShutdownInProgress: 91,
16+
PrimarySteppedDown: 189,
17+
ExceededTimeLimit: 262,
18+
SocketException: 9001,
19+
NotMaster: 10107,
20+
InterruptedAtShutdown: 11600,
21+
InterruptedDueToReplStateChange: 11602,
22+
NotMasterNoSlaveOk: 13435,
23+
NotMasterOrSecondary: 13436,
24+
StaleShardVersion: 63,
25+
StaleEpoch: 150,
26+
StaleConfig: 13388,
27+
RetryChangeStream: 234,
28+
FailedToSatisfyReadPreference: 133,
29+
CursorNotFound: 43,
30+
LegacyNotPrimary: 10058,
31+
WriteConcernFailed: 64,
32+
NamespaceNotFound: 26,
33+
IllegalOperation: 20,
34+
MaxTimeMSExpired: 50,
35+
UnknownReplWriteConcern: 79,
36+
UnsatisfiableWriteConcern: 100
37+
} as const);
38+
1039
// From spec@https://github.com/mongodb/specifications/blob/f93d78191f3db2898a59013a7ed5650352ef6da8/source/change-streams/change-streams.rst#resumable-error
11-
export const GET_MORE_RESUMABLE_CODES = new Set([
12-
6, // HostUnreachable
13-
7, // HostNotFound
14-
89, // NetworkTimeout
15-
91, // ShutdownInProgress
16-
189, // PrimarySteppedDown
17-
262, // ExceededTimeLimit
18-
9001, // SocketException
19-
10107, // NotMaster
20-
11600, // InterruptedAtShutdown
21-
11602, // InterruptedDueToReplStateChange
22-
13435, // NotMasterNoSlaveOk
23-
13436, // NotMasterOrSecondary
24-
63, // StaleShardVersion
25-
150, // StaleEpoch
26-
13388, // StaleConfig
27-
234, // RetryChangeStream
28-
133, // FailedToSatisfyReadPreference
29-
43 // CursorNotFound
40+
export const GET_MORE_RESUMABLE_CODES = new Set<number>([
41+
MONGODB_ERROR_CODES.HostUnreachable,
42+
MONGODB_ERROR_CODES.HostNotFound,
43+
MONGODB_ERROR_CODES.NetworkTimeout,
44+
MONGODB_ERROR_CODES.ShutdownInProgress,
45+
MONGODB_ERROR_CODES.PrimarySteppedDown,
46+
MONGODB_ERROR_CODES.ExceededTimeLimit,
47+
MONGODB_ERROR_CODES.SocketException,
48+
MONGODB_ERROR_CODES.NotMaster,
49+
MONGODB_ERROR_CODES.InterruptedAtShutdown,
50+
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
51+
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
52+
MONGODB_ERROR_CODES.NotMasterOrSecondary,
53+
MONGODB_ERROR_CODES.StaleShardVersion,
54+
MONGODB_ERROR_CODES.StaleEpoch,
55+
MONGODB_ERROR_CODES.StaleConfig,
56+
MONGODB_ERROR_CODES.RetryChangeStream,
57+
MONGODB_ERROR_CODES.FailedToSatisfyReadPreference,
58+
MONGODB_ERROR_CODES.CursorNotFound
3059
]);
3160

3261
/** @public */
@@ -244,33 +273,33 @@ export class MongoWriteConcernError extends MongoError {
244273
}
245274

246275
// see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
247-
const RETRYABLE_ERROR_CODES = new Set([
248-
6, // HostUnreachable
249-
7, // HostNotFound
250-
89, // NetworkTimeout
251-
91, // ShutdownInProgress
252-
189, // PrimarySteppedDown
253-
9001, // SocketException
254-
10107, // NotMaster
255-
11600, // InterruptedAtShutdown
256-
11602, // InterruptedDueToReplStateChange
257-
13435, // NotMasterNoSlaveOk
258-
13436 // NotMasterOrSecondary
276+
const RETRYABLE_ERROR_CODES = new Set<number>([
277+
MONGODB_ERROR_CODES.HostUnreachable,
278+
MONGODB_ERROR_CODES.HostNotFound,
279+
MONGODB_ERROR_CODES.NetworkTimeout,
280+
MONGODB_ERROR_CODES.ShutdownInProgress,
281+
MONGODB_ERROR_CODES.PrimarySteppedDown,
282+
MONGODB_ERROR_CODES.SocketException,
283+
MONGODB_ERROR_CODES.NotMaster,
284+
MONGODB_ERROR_CODES.InterruptedAtShutdown,
285+
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
286+
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
287+
MONGODB_ERROR_CODES.NotMasterOrSecondary
259288
]);
260289

261-
const RETRYABLE_WRITE_ERROR_CODES = new Set([
262-
11600, // InterruptedAtShutdown
263-
11602, // InterruptedDueToReplStateChange
264-
10107, // NotMaster
265-
13435, // NotMasterNoSlaveOk
266-
13436, // NotMasterOrSecondary
267-
189, // PrimarySteppedDown
268-
91, // ShutdownInProgress
269-
7, // HostNotFound
270-
6, // HostUnreachable
271-
89, // NetworkTimeout
272-
9001, // SocketException
273-
262 // ExceededTimeLimit
290+
const RETRYABLE_WRITE_ERROR_CODES = new Set<number>([
291+
MONGODB_ERROR_CODES.InterruptedAtShutdown,
292+
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
293+
MONGODB_ERROR_CODES.NotMaster,
294+
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
295+
MONGODB_ERROR_CODES.NotMasterOrSecondary,
296+
MONGODB_ERROR_CODES.PrimarySteppedDown,
297+
MONGODB_ERROR_CODES.ShutdownInProgress,
298+
MONGODB_ERROR_CODES.HostNotFound,
299+
MONGODB_ERROR_CODES.HostUnreachable,
300+
MONGODB_ERROR_CODES.NetworkTimeout,
301+
MONGODB_ERROR_CODES.SocketException,
302+
MONGODB_ERROR_CODES.ExceededTimeLimit
274303
]);
275304

276305
export function isRetryableWriteError(error: MongoError): boolean {
@@ -291,42 +320,45 @@ export function isRetryableError(error: MongoError): boolean {
291320
);
292321
}
293322

294-
const SDAM_RECOVERING_CODES = new Set([
295-
91, // ShutdownInProgress
296-
189, // PrimarySteppedDown
297-
11600, // InterruptedAtShutdown
298-
11602, // InterruptedDueToReplStateChange
299-
13436 // NotMasterOrSecondary
323+
const SDAM_RECOVERING_CODES = new Set<number>([
324+
MONGODB_ERROR_CODES.ShutdownInProgress,
325+
MONGODB_ERROR_CODES.PrimarySteppedDown,
326+
MONGODB_ERROR_CODES.InterruptedAtShutdown,
327+
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
328+
MONGODB_ERROR_CODES.NotMasterOrSecondary
300329
]);
301330

302-
const SDAM_NOTMASTER_CODES = new Set([
303-
10107, // NotMaster
304-
13435 // NotMasterNoSlaveOk
331+
const SDAM_NOTMASTER_CODES = new Set<number>([
332+
MONGODB_ERROR_CODES.NotMaster,
333+
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
334+
MONGODB_ERROR_CODES.LegacyNotPrimary
305335
]);
306336

307-
const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
308-
11600, // InterruptedAtShutdown
309-
91 // ShutdownInProgress
337+
const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set<number>([
338+
MONGODB_ERROR_CODES.InterruptedAtShutdown,
339+
MONGODB_ERROR_CODES.ShutdownInProgress
310340
]);
311341

312342
function isRecoveringError(err: MongoError) {
313-
if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
314-
return true;
343+
if (typeof err.code !== 'undefined') {
344+
// If any error code exists, we ignore the error.message
345+
return SDAM_RECOVERING_CODES.has(err.code);
315346
}
316347

317-
return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
348+
return /not master or secondary/.test(err.message) || /node is recovering/.test(err.message);
318349
}
319350

320351
function isNotMasterError(err: MongoError) {
321-
if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
322-
return true;
352+
if (typeof err.code !== 'undefined') {
353+
// If any error code exists, we ignore the error.message
354+
return SDAM_NOTMASTER_CODES.has(err.code);
323355
}
324356

325357
if (isRecoveringError(err)) {
326358
return false;
327359
}
328360

329-
return err.message.match(/not master/);
361+
return /not master/.test(err.message);
330362
}
331363

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

382+
if (typeof error.code !== 'undefined') {
383+
return isRecoveringError(error) || isNotMasterError(error);
384+
}
350385
if (isRecoveringError(error) || isNotMasterError(error)) {
351386
return true;
352387
}

src/gridfs-stream/upload.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as crypto from 'crypto';
22
import { Writable } from 'stream';
3-
import { MongoError, AnyError } from '../error';
3+
import { MongoError, AnyError, MONGODB_ERROR_CODES } from '../error';
44
import { WriteConcern } from './../write_concern';
55
import { PromiseProvider } from '../promise_provider';
66
import { ObjectId } from '../bson';
@@ -11,8 +11,6 @@ import type { GridFSBucket } from './index';
1111
import type { GridFSFile } from './download';
1212
import type { WriteConcernOptions } from '../write_concern';
1313

14-
const ERROR_NAMESPACE_NOT_FOUND = 26;
15-
1614
/** @public */
1715
export type TFileId = string | number | Document | ObjectId;
1816

@@ -256,7 +254,7 @@ function checkChunksIndex(stream: GridFSBucketWriteStream, callback: Callback):
256254
let index: { files_id: number; n: number };
257255
if (error) {
258256
// Collection doesn't exist so create index
259-
if (error instanceof MongoError && error.code === ERROR_NAMESPACE_NOT_FOUND) {
257+
if (error instanceof MongoError && error.code === MONGODB_ERROR_CODES.NamespaceNotFound) {
260258
index = { files_id: 1, n: 1 };
261259
stream.chunks.createIndex(index, { background: false, unique: true }, error => {
262260
if (error) {
@@ -349,7 +347,7 @@ function checkIndexes(stream: GridFSBucketWriteStream, callback: Callback): void
349347
let index: { filename: number; uploadDate: number };
350348
if (error) {
351349
// Collection doesn't exist so create index
352-
if (error instanceof MongoError && error.code === ERROR_NAMESPACE_NOT_FOUND) {
350+
if (error instanceof MongoError && error.code === MONGODB_ERROR_CODES.NamespaceNotFound) {
353351
index = { filename: 1, uploadDate: 1 };
354352
stream.files.createIndex(index, { background: false }, (error?: AnyError) => {
355353
if (error) {

src/operations/execute_operation.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ReadPreference } from '../read_preference';
2-
import { MongoError, isRetryableError } from '../error';
2+
import { MongoError, isRetryableError, MONGODB_ERROR_CODES } from '../error';
33
import { Aspect, AbstractOperation } from './operation';
44
import { maxWireVersion, maybePromise, Callback } from '../utils';
55
import { ServerType } from '../sdam/common';
@@ -8,7 +8,7 @@ import type { Topology } from '../sdam/topology';
88
import type { ClientSession } from '../sessions';
99
import type { Document } from '../bson';
1010

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

0 commit comments

Comments
 (0)