Skip to content

Commit 76de66e

Browse files
committed
Fix pool.getConnection race conditions
1 parent eb58eef commit 76de66e

File tree

3 files changed

+69
-68
lines changed

3 files changed

+69
-68
lines changed

Changes.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ you spot any mistakes.
77
## HEAD
88

99
* Fix `pool.end` race conditions #915
10+
* Fix `pool.getConnection` race conditions
1011

1112
## v2.5.0 (2014-09-07)
1213

lib/Pool.js

+59-63
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ function Pool(options) {
1212
this.config = options.config;
1313
this.config.connectionConfig.pool = this;
1414

15-
this._allConnections = [];
16-
this._freeConnections = [];
17-
this._connectionQueue = [];
18-
this._closed = false;
15+
this._acquiringConnections = [];
16+
this._allConnections = [];
17+
this._freeConnections = [];
18+
this._connectionQueue = [];
19+
this._closed = false;
1920
}
2021

2122
Pool.prototype.getConnection = function (cb) {
@@ -38,24 +39,22 @@ Pool.prototype.getConnection = function (cb) {
3839
if (this.config.connectionLimit === 0 || this._allConnections.length < this.config.connectionLimit) {
3940
connection = new PoolConnection(this, { config: this.config.newConnectionConfig() });
4041

42+
this._acquiringConnections.push(connection);
4143
this._allConnections.push(connection);
4244

43-
connection._pool = null;
44-
return connection.connect({timeout: this.config.acquireTimeout}, function (err) {
45+
return connection.connect({timeout: this.config.acquireTimeout}, function onConnect(err) {
46+
spliceConnection(pool._acquiringConnections, connection);
47+
4548
if (pool._closed) {
46-
connection.destroy();
47-
pool._removeConnection(connection);
48-
cb(new Error('Pool is closed.'));
49-
return;
49+
err = new Error('Pool is closed.');
5050
}
5151

5252
if (err) {
53-
pool._removeConnection(connection);
53+
pool._purgeConnection(connection);
5454
cb(err);
5555
return;
5656
}
5757

58-
connection._pool = pool;
5958
pool.emit('connection', connection);
6059
cb(null, connection);
6160
});
@@ -77,30 +76,33 @@ Pool.prototype.acquireConnection = function acquireConnection(connection, cb) {
7776

7877
var pool = this;
7978

80-
connection._pool = null;
81-
connection.ping({timeout: this.config.acquireTimeout}, function(err) {
82-
if (!err && !pool._closed) {
83-
connection._pool = pool;
84-
cb(null, connection);
85-
return;
86-
}
79+
this._acquiringConnections.push(connection);
8780

88-
connection.destroy();
81+
connection.ping({timeout: this.config.acquireTimeout}, function onPing(err) {
82+
spliceConnection(pool._acquiringConnections, connection);
8983

9084
if (pool._closed) {
91-
pool._removeConnection(connection);
92-
cb(new Error('Pool is closed.'));
85+
err = new Error('Pool is closed.');
86+
}
87+
88+
if (err) {
89+
pool._connectionQueue.unshift(cb);
90+
pool._purgeConnection(connection);
9391
return;
9492
}
9593

96-
pool._connectionQueue.unshift(cb);
97-
pool._removeConnection(connection);
94+
cb(null, connection);
9895
});
9996
};
10097

10198
Pool.prototype.releaseConnection = function releaseConnection(connection) {
10299
var cb;
103100

101+
if (this._acquiringConnections.indexOf(connection) !== -1) {
102+
// connection is being acquired
103+
return;
104+
}
105+
104106
if (connection._pool) {
105107
if (connection._pool !== this) {
106108
throw new Error('Connection released to wrong pool');
@@ -143,39 +145,26 @@ Pool.prototype.end = function (cb) {
143145
};
144146
}
145147

146-
var calledBack = false;
147-
var closedConnections = 0;
148-
var connection;
148+
var calledBack = false;
149+
var waitingClose = this._allConnections.length;
149150

150-
var endCB = function(err) {
151+
function onEnd(err) {
151152
if (calledBack) {
152153
return;
153154
}
154155

155-
if (err || this._allConnections.length === 0) {
156+
if (err || --waitingClose === 0) {
156157
calledBack = true;
157158
return cb(err);
158159
}
159-
}.bind(this);
160-
161-
if (this._allConnections.length === 0) {
162-
return process.nextTick(endCB);
163160
}
164161

165-
while (this._allConnections.length) {
166-
connection = this._allConnections[0];
167-
168-
if (connection._pool === this) {
169-
closedConnections++;
170-
connection._pool = null;
171-
connection._realEnd(endCB);
172-
}
173-
174-
this._removeConnection(connection);
162+
if (waitingClose === 0) {
163+
return process.nextTick(cb);
175164
}
176165

177-
if (closedConnections === 0) {
178-
return process.nextTick(endCB);
166+
while (this._allConnections.length !== 0) {
167+
this._purgeConnection(this._allConnections[0], onEnd);
179168
}
180169
};
181170

@@ -229,32 +218,31 @@ Pool.prototype._enqueueCallback = function _enqueueCallback(callback) {
229218
this.emit('enqueue');
230219
};
231220

232-
Pool.prototype._purgeConnection = function _purgeConnection(connection) {
233-
var pool = this;
221+
Pool.prototype._purgeConnection = function _purgeConnection(connection, callback) {
222+
var cb = callback || function () {};
234223

235-
connection._realEnd(function(err) {
236-
if (err) {
237-
connection.destroy();
238-
}
224+
if (connection.state === 'disconnected') {
225+
connection.destroy();
226+
}
239227

240-
pool._removeConnection(connection);
241-
});
228+
this._removeConnection(connection);
229+
230+
if (connection.state !== 'disconnected' && !connection._protocol._quitSequence) {
231+
connection._realEnd(cb);
232+
return;
233+
}
234+
235+
process.nextTick(cb);
242236
};
243237

244238
Pool.prototype._removeConnection = function(connection) {
245-
var index;
246-
247239
connection._pool = null;
248240

249-
if ((index = this._allConnections.indexOf(connection)) !== -1) {
250-
// Remove connection from all connections
251-
this._allConnections.splice(index, 1);
252-
}
241+
// Remove connection from all connections
242+
spliceConnection(this._allConnections, connection);
253243

254-
if ((index = this._freeConnections.indexOf(connection)) !== -1) {
255-
// Remove connection from free connections
256-
this._freeConnections.splice(index, 1);
257-
}
244+
// Remove connection from free connections
245+
spliceConnection(this._freeConnections, connection);
258246

259247
this.releaseConnection(connection);
260248
};
@@ -266,3 +254,11 @@ Pool.prototype.escape = function(value) {
266254
Pool.prototype.escapeId = function escapeId(value) {
267255
return mysql.escapeId(value, false);
268256
};
257+
258+
function spliceConnection(array, connection) {
259+
var index;
260+
if ((index = array.indexOf(connection)) !== -1) {
261+
// Remove connection from all connections
262+
array.splice(index, 1);
263+
}
264+
}

test/unit/pool/test-change-user-eject.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,35 @@ server.listen(common.fakeServerPort, function(err) {
1414
assert.ifError(err);
1515

1616
var conn0;
17+
var threadId;
1718
pool.getConnection(function(err, conn) {
1819
assert.ifError(err);
19-
assert.strictEqual(conn.threadId, 1);
20+
assert.ok(conn.threadId === 1 || conn.threadId === 2);
2021
conn0 = conn;
22+
threadId = conn.threadId;
2123
});
2224

2325
pool.getConnection(function(err, conn) {
2426
assert.ifError(err);
25-
assert.strictEqual(conn.threadId, 2);
27+
assert.ok(conn.threadId === 1 || conn.threadId === 2);
28+
29+
var threadId = conn.threadId;
2630

2731
conn.changeUser({user: 'user_2'}, function(err) {
2832
assert.ifError(err);
29-
assert.strictEqual(conn.threadId, 2);
33+
assert.strictEqual(conn.threadId, threadId);
3034
conn.release();
3135
conn0.release();
3236
});
3337
});
3438

3539
pool.getConnection(function(err, conn1) {
3640
assert.ifError(err);
37-
assert.strictEqual(conn1.threadId, 1);
41+
assert.strictEqual(conn1.threadId, 3);
3842

3943
pool.getConnection(function(err, conn2) {
4044
assert.ifError(err);
41-
assert.strictEqual(conn2.threadId, 3);
45+
assert.strictEqual(conn2.threadId, threadId);
4246
conn1.release();
4347
conn2.release();
4448

0 commit comments

Comments
 (0)