Skip to content

Commit 40bdde2

Browse files
committed
feat(pool): Add Minimum connections, and auto close extra connections
This adds a new configuration for desired minimum connections, to complement the connection limit. If the autoOpenConnections is enabled (default true), the pool will immediately open the desired minimum number of connections. Any connection opened above the minimum, who sits unused for a time specified in idleTimeout, will be automatically closed. This allows you to set up a minimum expectation of standard load for connections to your database, but also allow you to configure a higher maximum to handle unexpected burst traffic, and be able to close them automatically when the burst subsides.
1 parent b6175d8 commit 40bdde2

File tree

3 files changed

+88
-12
lines changed

3 files changed

+88
-12
lines changed

lib/connection.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ function Connection(opts) {
103103
});
104104

105105
this.stream.on('end', function() {
106+
this._closed = true;
106107
// we need to set this flag everywhere where we want connection to close
107108
if (connection._closing) {
108109
return;
@@ -946,8 +947,8 @@ Connection.prototype.serverHandshake = function serverHandshake(args) {
946947
Connection.prototype.end = function(callback) {
947948
var connection = this;
948949

950+
connection._closing = true;
949951
if (this.config.isServer) {
950-
connection._closing = true;
951952
var quitCmd = new EventEmitter();
952953
setImmediate(function() {
953954
connection.stream.end();

lib/pool.js

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var mysql = require('../index.js');
22

33
var EventEmitter = require('events').EventEmitter;
4+
var Timers = require('timers');
45
var Util = require('util');
56
var PoolConnection = require('./pool_connection.js');
67
var Queue = require('denque');
@@ -18,6 +19,24 @@ function Pool(options) {
1819
this._freeConnections = new Queue();
1920
this._connectionQueue = new Queue();
2021
this._closed = false;
22+
if (this.config.autoOpenConnections && this.config.minConnections) {
23+
var self = this;
24+
var opened = 0;
25+
for (var i = 0; i < this.config.minConnections; i++) {
26+
this.getConnection(function (err, conn) {
27+
if (conn) {
28+
process.nextTick(function () {
29+
if (++opened === self.config.minConnections) {
30+
self._openingConnections = false;
31+
}
32+
self.releaseConnection(conn);
33+
});
34+
}
35+
});
36+
}
37+
// This must be after the getConnection, or else its enqueued
38+
this._openingConnections = true;
39+
}
2140
}
2241

2342
Pool.prototype.getConnection = function(cb) {
@@ -26,6 +45,10 @@ Pool.prototype.getConnection = function(cb) {
2645
return cb(new Error('Pool is closed.'));
2746
});
2847
}
48+
if (this._openingConnections) {
49+
// We are opening a connect, use it when ready
50+
return this._connectionQueue.push(cb);
51+
}
2952

3053
var connection;
3154

@@ -82,7 +105,8 @@ Pool.prototype.getConnection = function(cb) {
82105
Pool.prototype.releaseConnection = function(connection) {
83106
var cb;
84107

85-
if (!connection._pool) {
108+
connection._lastReleased = Date.now();
109+
if (!connection._pool || connection._closed || connection._closing) {
86110
// The connection has been removed from the pool and is no longer good.
87111
if (this._connectionQueue.length) {
88112
cb = this._connectionQueue.shift();
@@ -95,6 +119,7 @@ Pool.prototype.releaseConnection = function(connection) {
95119
process.nextTick(cb.bind(null, null, connection));
96120
} else {
97121
this._freeConnections.push(connection);
122+
this._manageExpiredTimer();
98123
}
99124
};
100125

@@ -111,14 +136,14 @@ Pool.prototype.end = function(cb) {
111136

112137
var calledBack = false;
113138
var closedConnections = 0;
114-
var connection;
139+
var numConnections = this._allConnections.length;
115140

116141
var endCB = function(err) {
117142
if (calledBack) {
118143
return;
119144
}
120145

121-
if (err || ++closedConnections >= this._allConnections.length) {
146+
if (err || ++closedConnections >= numConnections) {
122147
calledBack = true;
123148
cb(err);
124149
return;
@@ -130,9 +155,9 @@ Pool.prototype.end = function(cb) {
130155
return;
131156
}
132157

133-
for (var i = 0; i < this._allConnections.length; i++) {
134-
connection = this._allConnections.get(i);
135-
connection._realEnd(endCB);
158+
var connection;
159+
while ((connection = this._allConnections.shift())) {
160+
this._closeConnection(connection, endCB);
136161
}
137162
};
138163

@@ -184,6 +209,39 @@ Pool.prototype.execute = function(sql, values, cb) {
184209
});
185210
};
186211

212+
Pool.prototype._manageExpiredTimer = function() {
213+
var hasExtra = this._allConnections.length > this.config.minConnections;
214+
if (hasExtra && !this._expiredTimer) {
215+
this._expiredTimer = Timers.setInterval(
216+
Pool.prototype._closeIdleConnections.bind(this),
217+
Math.min(15, this.config.idleTimeout) * 1000
218+
);
219+
} else if (!hasExtra && this._expiredTimer) {
220+
Timers.clearInterval(this._expiredTimer);
221+
this._expiredTimer = null;
222+
}
223+
};
224+
225+
Pool.prototype._closeIdleConnections = function() {
226+
var now = Date.now();
227+
var timeout = this.config.idleTimeout * 1000;
228+
var length = this._freeConnections.length;
229+
var numExtra = this._allConnections.length - this.config.minConnections;
230+
for (var i = 0; numExtra > 0 && i < length; i++) {
231+
var conn = this._freeConnections.get(i);
232+
233+
if (now > conn._lastReleased + timeout) {
234+
// This connection has been unused for longer than the timeout
235+
this._closeConnection(conn);
236+
// decrement i and length as the length will be reduced by 1 by closeConnection
237+
i--;
238+
length--;
239+
numExtra--;
240+
}
241+
}
242+
this._manageExpiredTimer();
243+
};
244+
187245
Pool.prototype._removeConnection = function(connection) {
188246
// Remove connection from all connections
189247
spliceConnection(this._allConnections, connection);
@@ -192,6 +250,13 @@ Pool.prototype._removeConnection = function(connection) {
192250
spliceConnection(this._freeConnections, connection);
193251

194252
this.releaseConnection(connection);
253+
254+
this._manageExpiredTimer();
255+
};
256+
257+
Pool.prototype._closeConnection = function (connection, cb) {
258+
connection._realEnd(cb);
259+
this._removeConnection(connection);
195260
};
196261

197262
Pool.prototype.format = function(sql, values) {

lib/pool_config.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,23 @@ function PoolConfig(options) {
66
options = ConnectionConfig.parseUrl(options);
77
}
88
this.connectionConfig = new ConnectionConfig(options);
9-
this.waitForConnections = options.waitForConnections === undefined
9+
this.waitForConnections = options.waitForConnections == null
1010
? true
1111
: Boolean(options.waitForConnections);
12-
this.connectionLimit = options.connectionLimit === undefined
12+
this.connectionLimit = options.connectionLimit == null
1313
? 10
14-
: Number(options.connectionLimit);
15-
this.queueLimit = options.queueLimit === undefined
14+
: Number(options.connectionLimit) || 10;
15+
this.queueLimit = options.queueLimit == null
1616
? 0
17-
: Number(options.queueLimit);
17+
: Number(options.queueLimit) || 0;
18+
this.minConnections = Math.min(
19+
this.connectionLimit,
20+
Number(options.minConnections) || 0
21+
);
22+
this.autoOpenConnections = options.autoOpenConnections == null
23+
? true
24+
: Boolean(options.autoOpenConnections);
25+
this.idleTimeout = options.idleTimeout == null
26+
? 300
27+
: Number(options.idleTimeout) || 300;
1828
}

0 commit comments

Comments
 (0)