Skip to content

Commit 2bd17a6

Browse files
committed
fix: destroy connections marked as closed on checkIn / checkOut
Connections which receive a `close` event are now marked as being in a `closed` state. The ConnectionPool will now check for this state, and destroy the connections accordingly.
1 parent 56aeb52 commit 2bd17a6

File tree

3 files changed

+56
-6
lines changed

3 files changed

+56
-6
lines changed

lib/cmap/connection.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const EventEmitter = require('events');
44
const MessageStream = require('./message_stream');
55
const MongoError = require('../core/error').MongoError;
6+
const MongoNetworkError = require('../core/error').MongoNetworkError;
67
const MongoWriteConcernError = require('../core/error').MongoWriteConcernError;
78
const wp = require('../core/wireprotocol');
89
const apm = require('../core/connection/apm');
@@ -26,6 +27,7 @@ class Connection extends EventEmitter {
2627
this.socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
2728
this.monitorCommands =
2829
typeof options.monitorCommands === 'boolean' ? options.monitorCommands : false;
30+
this.closed = false;
2931

3032
this[kGeneration] = options.generation;
3133
this[kLastUseTime] = Date.now();
@@ -40,7 +42,10 @@ class Connection extends EventEmitter {
4042
});
4143

4244
stream.on('close', () => {
43-
this[kQueue].forEach(op => op.cb(new MongoError('Connection closed')));
45+
this.closed = true;
46+
this[kQueue].forEach(op =>
47+
op.cb(new MongoNetworkError(`connection ${this.id} to ${this.address} closed`))
48+
);
4449
this[kQueue].clear();
4550

4651
this.emit('close');

lib/cmap/connection_pool.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,14 @@ class ConnectionPool extends EventEmitter {
165165
const connection = pool[kConnections].pop();
166166
const isStale = connectionIsStale(pool, connection);
167167
const isIdle = connectionIsIdle(pool, connection);
168-
if (!isStale && !isIdle) {
168+
if (!isStale && !isIdle && !connection.closed) {
169169
pool.emit('connectionCheckedOut', new ConnectionCheckedOutEvent(pool, connection));
170170
callback(null, connection);
171171
return;
172172
}
173173

174-
destroyConnection(pool, connection, isStale ? 'stale' : 'idle');
174+
const reason = connection.closed ? 'error' : isStale ? 'stale' : 'idle';
175+
destroyConnection(pool, connection, reason);
175176
}
176177

177178
if (maxPoolSize <= 0 || pool.totalConnectionCount < maxPoolSize) {
@@ -208,9 +209,9 @@ class ConnectionPool extends EventEmitter {
208209
* @param {Connection} connection The connection to check in
209210
*/
210211
checkIn(connection) {
211-
const closed = this.closed;
212+
const poolClosed = this.closed;
212213
const stale = connectionIsStale(this, connection);
213-
const willDestroy = !!(closed || stale);
214+
const willDestroy = !!(poolClosed || stale || connection.closed);
214215

215216
// Properly adjust state of connection
216217
if (!willDestroy) {
@@ -221,7 +222,8 @@ class ConnectionPool extends EventEmitter {
221222
this.emit('connectionCheckedIn', new ConnectionCheckedInEvent(this, connection));
222223

223224
if (willDestroy) {
224-
destroyConnection(this, connection, closed ? 'poolClosed' : 'stale');
225+
const reason = connection.closed ? 'error' : poolClosed ? 'poolClosed' : 'stale';
226+
destroyConnection(this, connection, reason);
225227
}
226228
}
227229

test/unit/cmap/connection_pool.test.js

+43
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,49 @@ describe('Connection Pool', function() {
4444
mock.createServer().then(s => (server = s));
4545
});
4646

47+
it('should destroy connections which have been closed', function(done) {
48+
server.setMessageHandler(request => {
49+
const doc = request.document;
50+
if (doc.ismaster) {
51+
request.reply(mock.DEFAULT_ISMASTER_36);
52+
} else {
53+
// destroy on any other command
54+
request.connection.destroy();
55+
}
56+
});
57+
58+
const pool = new ConnectionPool(
59+
Object.assign({ bson: new BSON(), maxPoolSize: 1 }, server.address())
60+
);
61+
62+
const events = [];
63+
pool.on('connectionClosed', event => events.push(event));
64+
65+
pool.checkOut((err, conn) => {
66+
expect(err).to.not.exist;
67+
68+
conn.command('admin.$cmd', { ping: 1 }, (err, result) => {
69+
expect(err).to.exist;
70+
expect(result).to.not.exist;
71+
72+
pool.checkIn(conn);
73+
74+
expect(events).to.have.length(1);
75+
const closeEvent = events[0];
76+
expect(closeEvent)
77+
.have.property('reason')
78+
.equal('error');
79+
80+
pool.close(done);
81+
});
82+
});
83+
84+
pool.withConnection((err, conn, cb) => {
85+
expect(err).to.not.exist;
86+
cb();
87+
});
88+
});
89+
4790
describe('withConnection', function() {
4891
it('should manage a connection for a successful operation', function(done) {
4992
server.setMessageHandler(request => {

0 commit comments

Comments
 (0)