Skip to content

Commit 9a176ef

Browse files
authored
fix: allow client re-connect after close (#2615)
`MongoClient#close` now removes the topology assigned to the client; when a client is re-connected, a new topology is created and added to the client. Existing references to Dbs and Collections can be re-used after client reconnect. NODE-2544
1 parent 4472308 commit 9a176ef

File tree

6 files changed

+71
-8
lines changed

6 files changed

+71
-8
lines changed

src/change_stream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,6 @@ function processNewChange(
588588
}
589589

590590
function processError(changeStream: ChangeStream, error: AnyError, callback?: Callback) {
591-
const topology = getTopology(changeStream.parent);
592591
const cursor = changeStream.cursor;
593592

594593
// If the change stream has been closed explicitly, do not process error.
@@ -618,6 +617,7 @@ function processError(changeStream: ChangeStream, error: AnyError, callback?: Ca
618617
// close internal cursor, ignore errors
619618
cursor.close();
620619

620+
const topology = getTopology(changeStream.parent);
621621
waitForTopologyConnected(topology, { readPreference: cursor.readPreference }, err => {
622622
// if the topology can't reconnect, close the stream
623623
if (err) return unresumableError(err);

src/mongo_client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,10 @@ export class MongoClient extends EventEmitter implements OperationParent {
362362
return cb();
363363
}
364364

365+
// clear out references to old topology
365366
const topology = this.topology;
367+
this.topology = undefined;
368+
366369
topology.close({ force }, err => {
367370
const autoEncrypter = topology.s.options.autoEncrypter;
368371
if (!autoEncrypter) {

src/operations/connect.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ export function connect(
197197
throw new Error('no callback function provided');
198198
}
199199

200+
// If a connection already been established, we can terminate early
201+
if (mongoClient.topology && mongoClient.topology.isConnected()) {
202+
return callback(undefined, mongoClient);
203+
}
204+
200205
let didRequestAuthentication = false;
201206
const logger = new Logger('MongoClient', options);
202207

test/functional/connection.test.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
2-
const test = require('./shared').assert,
3-
setupDatabase = require('./shared').setupDatabase,
4-
expect = require('chai').expect;
2+
const { withClient, setupDatabase } = require('./shared');
3+
const test = require('./shared').assert;
4+
const { expect } = require('chai');
55

66
describe('Connection', function () {
77
before(function () {
@@ -273,4 +273,58 @@ describe('Connection', function () {
273273
done();
274274
}
275275
});
276+
277+
it(
278+
'should be able to connect again after close',
279+
withClient(function (client, done) {
280+
expect(client.isConnected()).to.be.true;
281+
282+
const collection = client.db('shouldConnectAfterClose').collection('test');
283+
collection.insertOne({ a: 1, b: 2 }, (err, result) => {
284+
expect(err).to.not.exist;
285+
expect(result).to.exist;
286+
287+
client.close(err => {
288+
expect(err).to.not.exist;
289+
expect(client.isConnected()).to.be.false;
290+
291+
client.connect(err => {
292+
expect(err).to.not.exist;
293+
expect(client.isConnected()).to.be.true;
294+
295+
collection.findOne({ a: 1 }, (err, result) => {
296+
expect(err).to.not.exist;
297+
expect(result).to.exist;
298+
expect(result).to.have.property('a', 1);
299+
expect(result).to.have.property('b', 2);
300+
expect(client.topology.isDestroyed()).to.be.false;
301+
done();
302+
});
303+
});
304+
});
305+
});
306+
})
307+
);
308+
309+
it(
310+
'should correctly fail on retry when client has been closed',
311+
withClient(function (client, done) {
312+
expect(client.isConnected()).to.be.true;
313+
const collection = client.db('shouldCorrectlyFailOnRetry').collection('test');
314+
collection.insertOne({ a: 1 }, (err, result) => {
315+
expect(err).to.not.exist;
316+
expect(result).to.exist;
317+
318+
client.close(true, function (err) {
319+
expect(err).to.not.exist;
320+
expect(client.isConnected()).to.be.false;
321+
322+
expect(() => {
323+
collection.insertOne({ a: 2 });
324+
}).to.throw(/must be connected/);
325+
done();
326+
});
327+
});
328+
})
329+
);
276330
});

test/functional/find.test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,15 +2637,16 @@ describe('Find', function () {
26372637
expect(err).to.not.exist;
26382638

26392639
let selectedServer;
2640-
const selectServerStub = sinon.stub(client.topology, 'selectServer').callsFake(function () {
2640+
const topology = client.topology;
2641+
const selectServerStub = sinon.stub(topology, 'selectServer').callsFake(function () {
26412642
const args = Array.prototype.slice.call(arguments);
26422643
const originalCallback = args.pop();
26432644
args.push((err, server) => {
26442645
selectedServer = server;
26452646
originalCallback(err, server);
26462647
});
26472648

2648-
return client.topology.selectServer.wrappedMethod.apply(this, args);
2649+
return topology.selectServer.wrappedMethod.apply(this, args);
26492650
});
26502651

26512652
const collection = client.db().collection('test_read_preference');

test/functional/sessions.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ describe('Sessions', function () {
123123
// verify that the `endSessions` command was sent
124124
const lastCommand = test.commands.started[test.commands.started.length - 1];
125125
expect(lastCommand.commandName).to.equal('endSessions');
126-
expect(client.topology.s.sessionPool.sessions).to.have.length(0);
126+
expect(client.topology).to.not.exist;
127127
});
128128
});
129129
});
@@ -143,7 +143,7 @@ describe('Sessions', function () {
143143
// verify that the `endSessions` command was sent
144144
const lastCommand = test.commands.started[test.commands.started.length - 1];
145145
expect(lastCommand.commandName).to.equal('endSessions');
146-
expect(client.topology.s.sessionPool.sessions).to.have.length(0);
146+
expect(client.topology).to.not.exist;
147147
});
148148
});
149149
}

0 commit comments

Comments
 (0)