Skip to content

Commit 5bf0df8

Browse files
committed
refactor: improve error handling in Server type
This refactoring improves readability and reduces coupling between the `Server` and `Topology` types. Previously errors experienced at the `Server` level would emit an `error` event, which needed to be caught by the `Topology`. Since nothing about error handling required access to the `Topology` type, all of that behavior was moved back into the `Server` type. NODE-2449
1 parent 21195ce commit 5bf0df8

File tree

4 files changed

+27
-89
lines changed

4 files changed

+27
-89
lines changed

lib/core/sdam/server.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const collationNotSupported = require('../utils').collationNotSupported;
1414
const debugOptions = require('../connection/utils').debugOptions;
1515
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
1616
const isNetworkTimeoutError = require('../error').isNetworkTimeoutError;
17+
const isNodeShuttingDownError = require('../error').isNodeShuttingDownError;
18+
const maxWireVersion = require('../utils').maxWireVersion;
1719
const makeStateMachine = require('../utils').makeStateMachine;
1820
const common = require('./common');
1921

@@ -140,15 +142,7 @@ class Server extends EventEmitter {
140142
this.s.pool.clear();
141143
});
142144

143-
this[kMonitor].on('resetServer', error => {
144-
// Revert to an `Unknown` state by emitting a default description with no isMaster, and the
145-
// error from the heartbeat attempt
146-
this.emit(
147-
'descriptionReceived',
148-
new ServerDescription(this.description.address, null, { error })
149-
);
150-
});
151-
145+
this[kMonitor].on('resetServer', error => markServerUnknown(this, error));
152146
this[kMonitor].on('serverHeartbeatSucceeded', event => {
153147
this.emit(
154148
'descriptionReceived',
@@ -477,10 +471,16 @@ function makeOperationHandler(server, options, callback) {
477471
}
478472

479473
if (!isNetworkTimeoutError(err)) {
480-
server.emit('error', err);
474+
markServerUnknown(server, err);
475+
server.s.pool.clear();
481476
}
482477
} else if (isSDAMUnrecoverableError(err)) {
483-
server.emit('error', err);
478+
if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(err)) {
479+
server.s.pool.clear();
480+
}
481+
482+
markServerUnknown(server, err);
483+
process.nextTick(() => server.requestCheck());
484484
}
485485
}
486486

lib/core/sdam/topology.js

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ const deprecate = require('util').deprecate;
1515
const BSON = require('../connection/utils').retrieveBSON();
1616
const createCompressionInfo = require('../topologies/shared').createCompressionInfo;
1717
const isRetryableError = require('../error').isRetryableError;
18-
const isNodeShuttingDownError = require('../error').isNodeShuttingDownError;
19-
const maxWireVersion = require('../utils').maxWireVersion;
2018
const ClientSession = require('../sessions').ClientSession;
2119
const MongoError = require('../error').MongoError;
2220
const MongoServerSelectionError = require('../error').MongoServerSelectionError;
@@ -55,7 +53,7 @@ const SERVER_RELAY_EVENTS = [
5553
].concat(CMAP_EVENT_NAMES);
5654

5755
// all events we listen to from `Server` instances
58-
const LOCAL_SERVER_EVENTS = ['error', 'connect', 'descriptionReceived', 'close', 'ended'];
56+
const LOCAL_SERVER_EVENTS = ['connect', 'descriptionReceived', 'close', 'ended'];
5957

6058
const STATE_CLOSING = common.STATE_CLOSING;
6159
const STATE_CLOSED = common.STATE_CLOSED;
@@ -279,7 +277,7 @@ class Topology extends EventEmitter {
279277

280278
translateReadPreference(options);
281279
const readPreference = options.readPreference || ReadPreference.primary;
282-
this.selectServer(readPreferenceServerSelector(readPreference), options, (err, server) => {
280+
this.selectServer(readPreferenceServerSelector(readPreference), options, err => {
283281
if (err) {
284282
this.close();
285283

@@ -292,28 +290,11 @@ class Topology extends EventEmitter {
292290
return;
293291
}
294292

295-
const errorHandler = err => {
296-
stateTransition(this, STATE_CLOSED);
297-
server.removeListener('connect', connectHandler);
298-
if (typeof callback === 'function') callback(err, null);
299-
};
300-
301-
const connectHandler = (_, err) => {
302-
stateTransition(this, STATE_CONNECTED);
303-
server.removeListener('error', errorHandler);
304-
this.emit('open', err, this);
305-
this.emit('connect', this);
306-
307-
if (typeof callback === 'function') callback(err, this);
308-
};
309-
310-
if (server.s.state === STATE_CONNECTING) {
311-
server.once('error', errorHandler);
312-
server.once('connect', connectHandler);
313-
return;
314-
}
293+
stateTransition(this, STATE_CONNECTED);
294+
this.emit('open', err, this);
295+
this.emit('connect', this);
315296

316-
connectHandler();
297+
if (typeof callback === 'function') callback(err, this);
317298
});
318299
}
319300

@@ -794,9 +775,6 @@ function destroyServer(server, topology, options, callback) {
794775
options = options || {};
795776
LOCAL_SERVER_EVENTS.forEach(event => server.removeAllListeners(event));
796777

797-
// register a no-op for errors, we don't care now that we are destroying the server
798-
server.on('error', () => {});
799-
800778
server.destroy(options, () => {
801779
topology.emit(
802780
'serverClosed',
@@ -843,7 +821,6 @@ function createAndConnectServer(topology, serverDescription, connectDelay) {
843821
relayEvents(server, topology, SERVER_RELAY_EVENTS);
844822

845823
server.on('descriptionReceived', topology.serverUpdateHandler.bind(topology));
846-
server.on('error', serverErrorEventHandler(server, topology));
847824

848825
if (connectDelay) {
849826
const connectTimer = setTimeout(() => {
@@ -904,21 +881,6 @@ function updateServers(topology, incomingServerDescription) {
904881
}
905882
}
906883

907-
function serverErrorEventHandler(server, topology) {
908-
return function(err) {
909-
if (topology.s.state === STATE_CLOSING || topology.s.state === STATE_CLOSED) {
910-
return;
911-
}
912-
913-
if (maxWireVersion(server) >= 8 && !isNodeShuttingDownError(err)) {
914-
resetServerState(server, err);
915-
return;
916-
}
917-
918-
resetServerState(server, err, { clearPool: true });
919-
};
920-
}
921-
922884
function executeWriteOperation(args, options, callback) {
923885
if (typeof options === 'function') (callback = options), (options = {});
924886
options = options || {};
@@ -972,30 +934,6 @@ function executeWriteOperation(args, options, callback) {
972934
});
973935
}
974936

975-
/**
976-
* Resets the internal state of this server to `Unknown` by simulating an empty ismaster
977-
*
978-
* @private
979-
* @param {Server} server
980-
* @param {MongoError} error The error that caused the state reset
981-
* @param {object} [options] Optional settings
982-
* @param {boolean} [options.clearPool=false] Pool should be cleared out on state reset
983-
*/
984-
function resetServerState(server, error, options) {
985-
options = Object.assign({}, { clearPool: false }, options);
986-
987-
if (options.clearPool && server.s.pool) {
988-
server.s.pool.clear();
989-
}
990-
991-
server.emit(
992-
'descriptionReceived',
993-
new ServerDescription(server.description.address, null, { error })
994-
);
995-
996-
process.nextTick(() => server.requestCheck());
997-
}
998-
999937
function translateReadPreference(options) {
1000938
if (options.readPreference == null) {
1001939
return;

test/unit/sdam/srv_polling.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ describe('Mongos SRV Polling', function() {
361361
});
362362
});
363363

364-
srvPoller.trigger(recordSets[1]);
364+
process.nextTick(() => srvPoller.trigger(recordSets[1]));
365365
} catch (e) {
366366
done(e);
367367
}

test/unit/sdam/topology.test.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,16 @@ describe('Topology (unit)', function() {
139139
expect(err).to.not.exist;
140140
this.defer(() => topology.close());
141141

142-
let serverError;
143-
server.on('error', err => (serverError = err));
142+
let serverDescription;
143+
server.on('descriptionReceived', sd => (serverDescription = sd));
144144

145145
let poolCleared = false;
146146
topology.on('connectionPoolCleared', () => (poolCleared = true));
147147

148148
server.command('test.test', { insert: { a: 42 } }, (err, result) => {
149149
expect(result).to.not.exist;
150150
expect(err).to.exist;
151-
expect(err).to.eql(serverError);
151+
expect(err).to.eql(serverDescription.error);
152152
expect(poolCleared).to.be.true;
153153
done();
154154
});
@@ -176,16 +176,16 @@ describe('Topology (unit)', function() {
176176
expect(err).to.not.exist;
177177
this.defer(() => topology.close());
178178

179-
let serverError;
180-
server.on('error', err => (serverError = err));
179+
let serverDescription;
180+
server.on('descriptionReceived', sd => (serverDescription = sd));
181181

182182
let poolCleared = false;
183183
topology.on('connectionPoolCleared', () => (poolCleared = true));
184184

185185
server.command('test.test', { insert: { a: 42 } }, (err, result) => {
186186
expect(result).to.not.exist;
187187
expect(err).to.exist;
188-
expect(err).to.eql(serverError);
188+
expect(err).to.eql(serverDescription.error);
189189
expect(poolCleared).to.be.false;
190190
done();
191191
});
@@ -213,13 +213,13 @@ describe('Topology (unit)', function() {
213213
expect(err).to.not.exist;
214214
this.defer(() => topology.close());
215215

216-
let serverError;
217-
server.on('error', err => (serverError = err));
216+
let serverDescription;
217+
server.on('descriptionReceived', sd => (serverDescription = sd));
218218

219219
server.command('test.test', { insert: { a: 42 } }, (err, result) => {
220220
expect(result).to.not.exist;
221221
expect(err).to.exist;
222-
expect(err).to.eql(serverError);
222+
expect(err).to.eql(serverDescription.error);
223223
expect(server.description.type).to.equal('Unknown');
224224
done();
225225
});

0 commit comments

Comments
 (0)