Skip to content

Commit 86ae7da

Browse files
committed
feat(pool): Minimum connections, auto open connections, and close idle 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 86ae7da

File tree

3 files changed

+77
-12
lines changed

3 files changed

+77
-12
lines changed

lib/connection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,8 +946,8 @@ Connection.prototype.serverHandshake = function serverHandshake(args) {
946946
Connection.prototype.end = function(callback) {
947947
var connection = this;
948948

949+
connection._closing = true;
949950
if (this.config.isServer) {
950-
connection._closing = true;
951951
var quitCmd = new EventEmitter();
952952
setImmediate(function() {
953953
connection.stream.end();

lib/pool.js

Lines changed: 61 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,14 @@ function Pool(options) {
1819
this._freeConnections = new Queue();
1920
this._connectionQueue = new Queue();
2021
this._closed = false;
22+
if (this.config.autoOpenConnections) {
23+
var self = this;
24+
for (var i = 0; i < this.config.minConnections; i++) {
25+
this.getConnection(function (conn) {
26+
self.releaseConnection(conn);
27+
})
28+
}
29+
}
2130
}
2231

2332
Pool.prototype.getConnection = function(cb) {
@@ -82,6 +91,7 @@ Pool.prototype.getConnection = function(cb) {
8291
Pool.prototype.releaseConnection = function(connection) {
8392
var cb;
8493

94+
connection._lastReleased = Date.now();
8595
if (!connection._pool) {
8696
// The connection has been removed from the pool and is no longer good.
8797
if (this._connectionQueue.length) {
@@ -95,6 +105,7 @@ Pool.prototype.releaseConnection = function(connection) {
95105
process.nextTick(cb.bind(null, null, connection));
96106
} else {
97107
this._freeConnections.push(connection);
108+
this._manageExpiredTimer();
98109
}
99110
};
100111

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

112123
var calledBack = false;
113124
var closedConnections = 0;
114-
var connection;
125+
var numConnections = this._allConnections.length;
115126

116127
var endCB = function(err) {
117128
if (calledBack) {
118129
return;
119130
}
120131

121-
if (err || ++closedConnections >= this._allConnections.length) {
132+
if (err || ++closedConnections >= numConnections) {
122133
calledBack = true;
123134
cb(err);
124135
return;
@@ -130,9 +141,9 @@ Pool.prototype.end = function(cb) {
130141
return;
131142
}
132143

133-
for (var i = 0; i < this._allConnections.length; i++) {
134-
connection = this._allConnections.get(i);
135-
connection._realEnd(endCB);
144+
var connection;
145+
while ((connection = this._allConnections.shift())) {
146+
this._closeConnection(connection, endCB);
136147
}
137148
};
138149

@@ -184,14 +195,58 @@ Pool.prototype.execute = function(sql, values, cb) {
184195
});
185196
};
186197

198+
Pool.prototype._manageExpiredTimer = function() {
199+
var hasExtra = this._allConnections.length > this.config.minConnections;
200+
if (hasExtra && !this._expiredTimer) {
201+
this._expiredTimer = Timers.setInterval(
202+
Pool.prototype._closeIdleConnections.bind(this),
203+
15000
204+
);
205+
} else if (!hasExtra && this._expiredTimer) {
206+
Timers.clearInterval(this._expiredTimer);
207+
this._expiredTimer = null;
208+
}
209+
};
210+
211+
Pool.prototype._closeIdleConnections = function() {
212+
var now = Date.now();
213+
var timeout = this.config.idleTimeout * 1000;
214+
var length = this._freeConnections.length;
215+
var numExtra = this._allConnections.length - this.config.minConnections;
216+
for (var i = 0; i < length; i++) {
217+
if (numExtra <= 0) {
218+
break;
219+
}
220+
var conn = this._freeConnections.get(i);
221+
222+
if (now > conn._lastReleased + timeout) {
223+
// This connection has been unused for longer than the timeout
224+
this._closeConnection(conn);
225+
// decrement i and length as the length will be reduced by 1 by closeConnection
226+
i--;
227+
length--;
228+
}
229+
}
230+
this._manageExpiredTimer();
231+
};
232+
187233
Pool.prototype._removeConnection = function(connection) {
188234
// Remove connection from all connections
189235
spliceConnection(this._allConnections, connection);
190236

191237
// Remove connection from free connections
192238
spliceConnection(this._freeConnections, connection);
193239

194-
this.releaseConnection(connection);
240+
if (!connection._closing) {
241+
this.releaseConnection(connection);
242+
}
243+
244+
this._manageExpiredTimer();
245+
};
246+
247+
Pool.prototype._closeConnection = function (connection, cb) {
248+
connection._realEnd(cb);
249+
this._removeConnection(connection);
195250
};
196251

197252
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)