diff --git a/src/change_stream.ts b/src/change_stream.ts index 0972b3e1b61..bd5e3fe8a5a 100644 --- a/src/change_stream.ts +++ b/src/change_stream.ts @@ -588,7 +588,6 @@ function processNewChange( } function processError(changeStream: ChangeStream, error: AnyError, callback?: Callback) { - const topology = getTopology(changeStream.parent); const cursor = changeStream.cursor; // If the change stream has been closed explicitly, do not process error. @@ -618,6 +617,7 @@ function processError(changeStream: ChangeStream, error: AnyError, callback?: Ca // close internal cursor, ignore errors cursor.close(); + const topology = getTopology(changeStream.parent); waitForTopologyConnected(topology, { readPreference: cursor.readPreference }, err => { // if the topology can't reconnect, close the stream if (err) return unresumableError(err); diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 2cabbd30f4f..ad01cc1f041 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -362,7 +362,10 @@ export class MongoClient extends EventEmitter implements OperationParent { return cb(); } + // clear out references to old topology const topology = this.topology; + this.topology = undefined; + topology.close({ force }, err => { const autoEncrypter = topology.s.options.autoEncrypter; if (!autoEncrypter) { diff --git a/src/operations/connect.ts b/src/operations/connect.ts index 85700deb640..285c9670ae1 100644 --- a/src/operations/connect.ts +++ b/src/operations/connect.ts @@ -197,6 +197,11 @@ export function connect( throw new Error('no callback function provided'); } + // If a connection already been established, we can terminate early + if (mongoClient.topology && mongoClient.topology.isConnected()) { + return callback(undefined, mongoClient); + } + let didRequestAuthentication = false; const logger = new Logger('MongoClient', options); diff --git a/test/functional/connection.test.js b/test/functional/connection.test.js index 59e46c7e9cb..403d5ef4453 100644 --- a/test/functional/connection.test.js +++ b/test/functional/connection.test.js @@ -1,7 +1,7 @@ 'use strict'; -const test = require('./shared').assert, - setupDatabase = require('./shared').setupDatabase, - expect = require('chai').expect; +const { withClient, setupDatabase } = require('./shared'); +const test = require('./shared').assert; +const { expect } = require('chai'); describe('Connection', function () { before(function () { @@ -273,4 +273,58 @@ describe('Connection', function () { done(); } }); + + it( + 'should be able to connect again after close', + withClient(function (client, done) { + expect(client.isConnected()).to.be.true; + + const collection = client.db('shouldConnectAfterClose').collection('test'); + collection.insertOne({ a: 1, b: 2 }, (err, result) => { + expect(err).to.not.exist; + expect(result).to.exist; + + client.close(err => { + expect(err).to.not.exist; + expect(client.isConnected()).to.be.false; + + client.connect(err => { + expect(err).to.not.exist; + expect(client.isConnected()).to.be.true; + + collection.findOne({ a: 1 }, (err, result) => { + expect(err).to.not.exist; + expect(result).to.exist; + expect(result).to.have.property('a', 1); + expect(result).to.have.property('b', 2); + expect(client.topology.isDestroyed()).to.be.false; + done(); + }); + }); + }); + }); + }) + ); + + it( + 'should correctly fail on retry when client has been closed', + withClient(function (client, done) { + expect(client.isConnected()).to.be.true; + const collection = client.db('shouldCorrectlyFailOnRetry').collection('test'); + collection.insertOne({ a: 1 }, (err, result) => { + expect(err).to.not.exist; + expect(result).to.exist; + + client.close(true, function (err) { + expect(err).to.not.exist; + expect(client.isConnected()).to.be.false; + + expect(() => { + collection.insertOne({ a: 2 }); + }).to.throw(/must be connected/); + done(); + }); + }); + }) + ); }); diff --git a/test/functional/find.test.js b/test/functional/find.test.js index 5ebfb4ece18..dc51bb476fd 100644 --- a/test/functional/find.test.js +++ b/test/functional/find.test.js @@ -2637,7 +2637,8 @@ describe('Find', function () { expect(err).to.not.exist; let selectedServer; - const selectServerStub = sinon.stub(client.topology, 'selectServer').callsFake(function () { + const topology = client.topology; + const selectServerStub = sinon.stub(topology, 'selectServer').callsFake(function () { const args = Array.prototype.slice.call(arguments); const originalCallback = args.pop(); args.push((err, server) => { @@ -2645,7 +2646,7 @@ describe('Find', function () { originalCallback(err, server); }); - return client.topology.selectServer.wrappedMethod.apply(this, args); + return topology.selectServer.wrappedMethod.apply(this, args); }); const collection = client.db().collection('test_read_preference'); diff --git a/test/functional/sessions.test.js b/test/functional/sessions.test.js index 2751019bfb9..eb25a46ec87 100644 --- a/test/functional/sessions.test.js +++ b/test/functional/sessions.test.js @@ -123,7 +123,7 @@ describe('Sessions', function () { // verify that the `endSessions` command was sent const lastCommand = test.commands.started[test.commands.started.length - 1]; expect(lastCommand.commandName).to.equal('endSessions'); - expect(client.topology.s.sessionPool.sessions).to.have.length(0); + expect(client.topology).to.not.exist; }); }); }); @@ -143,7 +143,7 @@ describe('Sessions', function () { // verify that the `endSessions` command was sent const lastCommand = test.commands.started[test.commands.started.length - 1]; expect(lastCommand.commandName).to.equal('endSessions'); - expect(client.topology.s.sessionPool.sessions).to.have.length(0); + expect(client.topology).to.not.exist; }); }); }