Skip to content

Commit 844c2c8

Browse files
authored
fix(change_stream): do not check isGetMore if error[mongoErrorContextSymbol] is undefined (#1720)
Fixes NODE-1494
1 parent 1dd1e87 commit 844c2c8

File tree

4 files changed

+58
-45
lines changed

4 files changed

+58
-45
lines changed

lib/change_stream.js

+1-32
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
const EventEmitter = require('events');
44
const inherits = require('util').inherits;
5-
const MongoNetworkError = require('mongodb-core').MongoNetworkError;
6-
const mongoErrorContextSymbol = require('mongodb-core').mongoErrorContextSymbol;
7-
const GET_MORE_NON_RESUMABLE_CODES = require('./error_codes').GET_MORE_NON_RESUMABLE_CODES;
5+
const isResumableError = require('./error').isResumableError;
86

97
var cursorOptionNames = ['maxAwaitTimeMS', 'collation', 'readPreference'];
108

@@ -298,35 +296,6 @@ ChangeStream.prototype.stream = function(options) {
298296
return this.cursor.stream(options);
299297
};
300298

301-
// From spec@https://github.com/mongodb/specifications/blob/35e466ddf25059cb30e4113de71cdebd3754657f/source/change-streams.rst#resumable-error:
302-
//
303-
// An error is considered resumable if it meets any of the following criteria:
304-
// - any error encountered which is not a server error (e.g. a timeout error or network error)
305-
// - any server error response from a getMore command excluding those containing the following error codes
306-
// - Interrupted: 11601
307-
// - CappedPositionLost: 136
308-
// - CursorKilled: 237
309-
// - a server error response with an error message containing the substring "not master" or "node is recovering"
310-
//
311-
// An error on an aggregate command is not a resumable error. Only errors on a getMore command may be considered resumable errors.
312-
313-
function isGetMoreError(error) {
314-
return !!error[mongoErrorContextSymbol].isGetMore;
315-
}
316-
317-
function isResumableError(error) {
318-
if (!isGetMoreError(error)) {
319-
return false;
320-
}
321-
322-
return !!(
323-
error instanceof MongoNetworkError ||
324-
!GET_MORE_NON_RESUMABLE_CODES.has(error.code) ||
325-
error.message.match(/not master/) ||
326-
error.message.match(/node is recovering/)
327-
);
328-
}
329-
330299
// Handle new change events. This method brings together the routes from the callback, event emitter, and promise ways of using ChangeStream.
331300
var processNewChange = function(self, err, change, callback) {
332301
// Handle errors

lib/error.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
3+
const MongoNetworkError = require('mongodb-core').MongoNetworkError;
4+
const mongoErrorContextSymbol = require('mongodb-core').mongoErrorContextSymbol;
5+
6+
const GET_MORE_NON_RESUMABLE_CODES = new Set([
7+
136, // CappedPositionLost
8+
237, // CursorKilled
9+
11601 // Interrupted
10+
]);
11+
12+
// From spec@https://github.com/mongodb/specifications/blob/35e466ddf25059cb30e4113de71cdebd3754657f/source/change-streams.rst#resumable-error:
13+
//
14+
// An error is considered resumable if it meets any of the following criteria:
15+
// - any error encountered which is not a server error (e.g. a timeout error or network error)
16+
// - any server error response from a getMore command excluding those containing the following error codes
17+
// - Interrupted: 11601
18+
// - CappedPositionLost: 136
19+
// - CursorKilled: 237
20+
// - a server error response with an error message containing the substring "not master" or "node is recovering"
21+
//
22+
// An error on an aggregate command is not a resumable error. Only errors on a getMore command may be considered resumable errors.
23+
24+
function isGetMoreError(error) {
25+
if (error[mongoErrorContextSymbol]) {
26+
return error[mongoErrorContextSymbol].isGetMore;
27+
}
28+
}
29+
30+
function isResumableError(error) {
31+
if (!isGetMoreError(error)) {
32+
return false;
33+
}
34+
35+
return !!(
36+
error instanceof MongoNetworkError ||
37+
!GET_MORE_NON_RESUMABLE_CODES.has(error.code) ||
38+
error.message.match(/not master/) ||
39+
error.message.match(/node is recovering/)
40+
);
41+
}
42+
43+
module.exports = { GET_MORE_NON_RESUMABLE_CODES, isResumableError };

lib/error_codes.js

-9
This file was deleted.

test/unit/change_stream_resume_tests.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const MongoClient = require('../../lib/mongo_client');
66
const ObjectId = require('../../index').ObjectId;
77
const Timestamp = require('../../index').Timestamp;
88
const Long = require('../../index').Long;
9-
const GET_MORE_NON_RESUMABLE_CODES = require('../../lib/error_codes').GET_MORE_NON_RESUMABLE_CODES;
9+
const GET_MORE_NON_RESUMABLE_CODES = require('../../lib/error').GET_MORE_NON_RESUMABLE_CODES;
10+
const isResumableError = require('../../lib/error').isResumableError;
1011

1112
describe('Change Stream Resume Tests', function() {
1213
const test = {};
@@ -126,15 +127,15 @@ describe('Change Stream Resume Tests', function() {
126127
secondGetMore: req => req.reply(GET_MORE_RESPONSE)
127128
},
128129
{
129-
description: `should resume on an error that says "not master"`,
130+
description: `should resume on an error that says 'not master'`,
130131
passing: true,
131132
firstAggregate: req => req.reply(AGGREGATE_RESPONSE),
132133
secondAggregate: req => req.reply(AGGREGATE_RESPONSE),
133134
firstGetMore: req => req.reply({ ok: 0, errmsg: 'not master' }),
134135
secondGetMore: req => req.reply(GET_MORE_RESPONSE)
135136
},
136137
{
137-
description: `should resume on an error that says "node is recovering"`,
138+
description: `should resume on an error that says 'node is recovering'`,
138139
passing: true,
139140
firstAggregate: req => req.reply(AGGREGATE_RESPONSE),
140141
secondAggregate: req => req.reply(AGGREGATE_RESPONSE),
@@ -175,14 +176,17 @@ describe('Change Stream Resume Tests', function() {
175176
test.server = server;
176177
});
177178
});
179+
178180
afterEach(done => changeStream.close(() => client.close(() => mock.cleanup(done))));
179181

180182
configs.forEach(config => {
181183
it(config.description, {
182184
metadata: { requires: { mongodb: '>=3.6.0' } },
183185
test: function() {
184186
test.server.setMessageHandler(makeServerHandler(config));
185-
client = new MongoClient(`mongodb://${test.server.uri()}`, { socketTimeoutMS: 300 });
187+
client = new MongoClient(`mongodb://${test.server.uri()}`, {
188+
socketTimeoutMS: 300
189+
});
186190
return client
187191
.connect()
188192
.then(client => client.db('test'))
@@ -210,3 +214,9 @@ describe('Change Stream Resume Tests', function() {
210214
});
211215
});
212216
});
217+
218+
describe('Change Stream Resume Error Tests', function() {
219+
it('should properly process errors that lack the `mongoErrorContextSymbol`', function() {
220+
expect(() => isResumableError(new Error())).to.not.throw();
221+
});
222+
});

0 commit comments

Comments
 (0)