Skip to content

Commit 50c72bd

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 50c72bd

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,22 @@ function Pool(options) {
1819
this._freeConnections = new Queue();
1920
this._connectionQueue = new Queue();
2021
this._closed = false;
22+
if (this.config.autoOpenConnections) {
23+
this._openingConnections = true;
24+
var self = this;
25+
var opened = 0;
26+
for (var i = 0; i < this.config.minConnections; i++) {
27+
this.getConnection(function (conn) {
28+
process.nextTick(function() {
29+
self.releaseConnection(conn);
30+
if (++opened === self.config.minConnections) {
31+
self._openingConnections = false;
32+
}
33+
});
34+
});
35+
}
36+
37+
}
2138
}
2239

2340
Pool.prototype.getConnection = function(cb) {
@@ -26,6 +43,10 @@ Pool.prototype.getConnection = function(cb) {
2643
return cb(new Error('Pool is closed.'));
2744
});
2845
}
46+
if (this._openingConnections) {
47+
// We are opening a connect, use it when ready
48+
return this._connectionQueue.push(cb);
49+
}
2950

3051
var connection;
3152

@@ -82,6 +103,7 @@ Pool.prototype.getConnection = function(cb) {
82103
Pool.prototype.releaseConnection = function(connection) {
83104
var cb;
84105

106+
connection._lastReleased = Date.now();
85107
if (!connection._pool) {
86108
// The connection has been removed from the pool and is no longer good.
87109
if (this._connectionQueue.length) {
@@ -95,6 +117,7 @@ Pool.prototype.releaseConnection = function(connection) {
95117
process.nextTick(cb.bind(null, null, connection));
96118
} else {
97119
this._freeConnections.push(connection);
120+
this._manageExpiredTimer();
98121
}
99122
};
100123

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

112135
var calledBack = false;
113136
var closedConnections = 0;
114-
var connection;
137+
var numConnections = this._allConnections.length;
115138

116139
var endCB = function(err) {
117140
if (calledBack) {
118141
return;
119142
}
120143

121-
if (err || ++closedConnections >= this._allConnections.length) {
144+
if (err || ++closedConnections >= numConnections) {
122145
calledBack = true;
123146
cb(err);
124147
return;
@@ -130,9 +153,9 @@ Pool.prototype.end = function(cb) {
130153
return;
131154
}
132155

133-
for (var i = 0; i < this._allConnections.length; i++) {
134-
connection = this._allConnections.get(i);
135-
connection._realEnd(endCB);
156+
var connection;
157+
while ((connection = this._allConnections.shift())) {
158+
this._closeConnection(connection, endCB);
136159
}
137160
};
138161

@@ -184,14 +207,56 @@ Pool.prototype.execute = function(sql, values, cb) {
184207
});
185208
};
186209

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

191247
// Remove connection from free connections
192248
spliceConnection(this._freeConnections, connection);
193249

194-
this.releaseConnection(connection);
250+
if (!connection._closing && !connection._closed) {
251+
this.releaseConnection(connection);
252+
}
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)