Skip to content

Commit 94f472f

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 f2829ce commit 94f472f

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-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: 59 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,56 @@ 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+
Math.min(15, this.config.idleTimeout) * 1000
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; numExtra > 0 && i < length; i++) {
217+
var conn = this._freeConnections.get(i);
218+
219+
if (now > conn._lastReleased + timeout) {
220+
// This connection has been unused for longer than the timeout
221+
this._closeConnection(conn);
222+
// decrement i and length as the length will be reduced by 1 by closeConnection
223+
i--;
224+
length--;
225+
numExtra--;
226+
}
227+
}
228+
this._manageExpiredTimer();
229+
};
230+
187231
Pool.prototype._removeConnection = function(connection) {
188232
// Remove connection from all connections
189233
spliceConnection(this._allConnections, connection);
190234

191235
// Remove connection from free connections
192236
spliceConnection(this._freeConnections, connection);
193237

194-
this.releaseConnection(connection);
238+
if (!connection._closing && !connection._closed) {
239+
this.releaseConnection(connection);
240+
}
241+
242+
this._manageExpiredTimer();
243+
};
244+
245+
Pool.prototype._closeConnection = function (connection, cb) {
246+
connection._realEnd(cb);
247+
this._removeConnection(connection);
195248
};
196249

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