mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-28 17:22:01 +08:00
fix(core): Guard connection pool idle checks during cleanup (#31059)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user