-
-
Notifications
You must be signed in to change notification settings - Fork 652
Description
When connection.release() is called multiple times, there is no logic implemented to properly handle the repeated release of the same connection.
Below is the source code for the releaseConnection method:
releaseConnection(connection) {
let cb;
if (!connection._pool) {
// The connection has been removed from the pool and is no longer good.
if (this._connectionQueue.length) {
cb = this._connectionQueue.shift();
process.nextTick(this.getConnection.bind(this, cb));
}
} else if (this._connectionQueue.length) {
cb = this._connectionQueue.shift();
process.nextTick(cb.bind(null, null, connection));
} else {
this._freeConnections.push(connection);
this.emit('release', connection);
}
}Each repeated call to releaseConnection either results in a duplicate reference to the same connection being added to the _freeConnections queue, or — if there are pending handlers in the _connectionQueue — in assigning the same connection to multiple different request handlers (cb).
This leads to the following issues:
-
Assigning the same connection to multiple handlers
Re-adding the same connection object to _freeConnections may cause the getConnection method to return the same connection to two different handlers.
This breaks the isolation of the handler and can lead to, for example, a violation of transaction isolation. -
Infinite loop when calling _removeIdleTimeoutConnections (CPU utilization 100% !!!)
If there are multiple references to the same connection in _freeConnections, the following happens:
- On the first iteration,
connection._poolis set tonull(connection._pool = null), and theconnectionis removed from the_freeConnectionsqueue. - On the next iteration, another reference to the same
connectionis processed again.
However, in the connection.destroy() → connection._removeFromPool() flow, removal from_freeConnectionsdoes not happen due to the!this._poolcondition in_removeFromPool.
As a result, the connection remains in the_freeConnectionsqueue, causing an infinite processing loop
Here are some illustrative tests:
const createPool = require('./common.test.cjs').createPool;
const { assert } = require('poku');
const pool = new createPool({
connectionLimit: 5,
maxIdle: 5,
idleTimeout: 2000,
});
pool.getConnection((_err1, connection1) => {
connection1.release();
connection1.release();
setTimeout(() => {
try {
assert(pool._freeConnections.length === 1, 'expect 1 connections');
} finally {
pool.end();
}
}, 300);
});const createPool = require('./common.test.cjs').createPool;
const { assert } = require('poku');
const pool = new createPool({
connectionLimit: 2,
maxIdle: 1,
idleTimeout: 3_000,
});
pool.getConnection((_err1, connection1) => {
connection1.release();
connection1.release();
setTimeout(() => {
pool.getConnection((_err1, connection1) => {
pool.getConnection((_err1, connection2) => {
try {
assert(connection1 !== connection2, 'Should be uniq connections');
} finally {
pool.end();
}
})
})
}, 500);
});const createPool = require('./common.test.cjs').createPool;
const { assert } = require('poku');
const pool = new createPool({
connectionLimit: 2,
maxIdle: 1,
idleTimeout: 500,
});
pool.getConnection((_err1, connection1) => {
connection1.release();
connection1.release();
setTimeout(() => {
try {
assert(true, 'No infinite loop');
} finally {
pool.end();
}
}, 3000);
});