Infinite loop on the connection pool in Thin mode (See https://github.com/oracle/node-oracledb/pull/1783)
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 2.4k
- Forks
- 1.1k
- PR merge metrics
- No merged PRs in 30d
Description
Picked from PR #1783
Hi, there is an infinite loop bug that occurs in the bgThreadFunc() method:
I encountered this issue while analyzing a problem in a production environment where pods were freezing and restarting, this issue only occurs in services that handle high volumes of message processing via queues.Mode: Thin
Problem:
When we configure a low number of connections in the pool (1, 4, 8, etc.) and the application experiences high concurrency (queue/event processing), it enters an infinite loop due to this part of code:
pool.jsasync bgThreadFunc() { while (!this._poolCloseWaiter) { const numToCreate = this._getNumConnsToCreate(); //when here returns 0 for (let i = 0; i < numToCreate; i++) { ... } // and here has this._pendingRequests.length > 0 //then we have an infinite loop if ((this._pendingRequests.length == 0 || this._bgErr) && this.getConnectionsOpen() >= this._poolMin) { await new Promise((resolve) => { this.bgWaiter = resolve; }); this.bgWaiter = null; } ... } }How to reproduce:
This scenario only occurs in situations of high concurrency; I’ll leave an example here for you to reproduce the problem.
(this code is only trying to simulate an queue/event concurrent processing)'use strict';
const oracledb = require('oracledb');
const EventEmitter = require('events');
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
const {
DB_USER = 'your_user',
DB_PASSWORD = 'your_pass',
DB_CONNECT_STRING = 'your_connection_string',
} = process.env;
class Broker extends EventEmitter { }
let pool;
async function createTableIfNotExists(connection) {
const plsql =BEGIN EXECUTE IMMEDIATE ' CREATE TABLE accounts ( id NUMBER PRIMARY KEY, balance NUMBER NOT NULL, updated_at TIMESTAMP DEFAULT SYSTIMESTAMP, version NUMBER DEFAULT 0 ) '; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -955 THEN RAISE; END IF; END;;
await connection.execute(plsql);
}async function processMessage(id) {
let connection;
try {
connection = await pool.getConnection();
await connection.execute(
UPDATE accounts SET balance = balance + :delta, version = version + 1 WHERE id = :id,
{ delta: 10, id: 1 }
);
} catch (err) {
console.error([${id}] error:, err.message);
} finally {
if (connection) {
try {
await connection.close();
console.log([${id}] connection released);
} catch (error) {
console.error([${id}] error when trying to close connection:, error.message);
}
}
}
}async function main() {
const broker = new Broker();
broker.on('message', (id) => {
processMessage(id);
});
pool = await oracledb.createPool({
user: DB_USER,
password: DB_PASSWORD,
connectString: DB_CONNECT_STRING,
poolMin: 4,
poolMax: 4,
queueMax: 100000,
poolIncrement: 1,
enableStatistics: true,
});
await createTableIfNotExists(await pool.getConnection());
setInterval(() => {
console.log(JSON.stringify(pool.getStatistics()));
}, 1000);
setInterval(() => {
for (let j = 0; j < 5; j++) {
broker.emit('message', j);
}
}, 2);
}
main().catch(console.error);
Solution:I left a suggested three-part solution in the code:
1 - In bgThreadFunc, check only for cases where numToCreate <= 0 to perform the await and avoid entering sync infinite loop.
2 - When calling release(), check for any pending tasks and attempt to reuse and resolve them immediately.
3 - When there is a pending task, we must wake up bgWaiter().
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in pool.js at bgThreadFunc(), then trace release() and the pending-request handling described in the issue. Reproduce the high-concurrency Thin-mode scenario with the provided queue example and verify that pending requests are serviced, the background waiter wakes, and the connection pool no longer enters an infinite loop.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100