-
-
Notifications
You must be signed in to change notification settings - Fork 641
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._pool
is set tonull
(connection._pool = null
), and theconnection
is removed from the_freeConnections
queue. - On the next iteration, another reference to the same
connection
is processed again.
However, in the connection.destroy() → connection._removeFromPool() flow, removal from_freeConnections
does not happen due to the!this._pool
condition in_removeFromPool
.
As a result, the connection remains in the_freeConnections
queue, 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);
});