fix(core): Guard connection pool idle checks during cleanup (#31059)

This commit is contained in:
Sudarshan Soma
2026-06-30 15:06:15 +05:30
committed by GitHub
parent 5df5a2c277
commit 84ee38eb0c
3 changed files with 55 additions and 1 deletions
@@ -58,6 +58,7 @@ export async function configureOracleDB(
nodeType: 'oracledb',
nodeVersion: String(options.nodeVersion ?? '1'),
fallBackHandler,
isIdle: (pool) => pool.connectionsInUse === 0,
wasUsed: (pool) => {
if (pool) {
this.logger.debug(`DB pool reused, open connections: ${pool.connectionsOpen}`);
@@ -190,6 +190,45 @@ describe('getConnection', () => {
expect(abortController.signal.aborted).toBe(true);
});
test('postpones stale cleanup while pool is not idle', async () => {
// ARRANGE
const connectionType = {};
let isPoolBusy = true;
let abortController: AbortController | undefined;
const fallBackHandler = vi.fn(async (ac: AbortController) => {
abortController = ac;
return connectionType;
});
const isIdle = vi.fn(() => !isPoolBusy);
await cpm.getConnection({
credentials: {},
nodeType: 'example',
nodeVersion: '1',
fallBackHandler,
isIdle,
wasUsed: vi.fn(),
});
// ACT 1
vi.advanceTimersByTime(ttl + cleanUpInterval * 2);
// ASSERT 1
if (abortController === undefined) {
expect.fail("abortController haven't been initialized");
}
const controller = abortController;
expect(isIdle).toHaveBeenCalledWith(connectionType);
expect(controller.signal.aborted).toBe(false);
// ACT 2
isPoolBusy = false;
vi.advanceTimersByTime(ttl + cleanUpInterval * 2);
// ASSERT 2
expect(controller.signal.aborted).toBe(true);
});
test('throws OperationsError if the fallBackHandler aborts during connection initialization', async () => {
// ARRANGE
const connectionType = {};
@@ -25,6 +25,12 @@ type GetConnectionOption<Pool> = RegistrationOptions & {
*/
fallBackHandler: (abortController: AbortController) => Promise<Pool>;
/**
* Returns whether the pool can be safely cleaned up. If omitted, stale pools
* are assumed to be idle.
*/
isIdle?: (pool: Pool) => boolean;
wasUsed: (pool: Pool) => void;
};
@@ -34,6 +40,7 @@ type Registration<Pool> = {
abortController: AbortController;
isIdle?: (pool: Pool) => boolean;
/** We keep this timestamp to check if a pool hasn't been used in a while, and if it needs to be closed */
lastUsed: number;
};
@@ -109,6 +116,7 @@ export class ConnectionPoolManager {
value = {
pool: await options.fallBackHandler(abortController),
abortController,
isIdle: options.isIdle,
} as Registration<unknown>;
// It's possible that `options.fallBackHandler` already called the abort
@@ -143,8 +151,14 @@ export class ConnectionPoolManager {
*/
private cleanupStaleConnections() {
const now = Date.now();
for (const [key, { lastUsed }] of this.map.entries()) {
for (const [key, registration] of this.map.entries()) {
const { isIdle, lastUsed, pool } = registration;
if (now - lastUsed > ttl) {
if (isIdle && !isIdle(pool)) {
registration.lastUsed = now;
this.logger.debug('ConnectionPoolManager: Found stale pool, but it is still in use.');
continue;
}
this.logger.debug('ConnectionPoolManager: Found stale pool. Cleaning it up.');
void this.cleanupConnection(key);
}