Fix pg pool implementation (#12106)

Fix the following error: 
Cannot use a pool after calling end on a pool

<img width="917" alt="Screenshot 2025-05-17 at 14 56 18"
src="https://github.com/user-attachments/assets/63081831-9a7e-4633-8274-de9f8a48dbae"
/>

The problem was that the datasource manager was destroying the
connections when a datasource cache expired.
This commit is contained in:
Félix Malfait
2025-05-17 15:22:10 +02:00
committed by GitHub
parent d93024fd02
commit 64d988cdec
2 changed files with 30 additions and 16 deletions

View File

@ -40,7 +40,7 @@ type CacheResult<T, U> = {
data: U; data: U;
}; };
const ONE_HOUR_IN_MS = 3600_000; const TWENTY_MINUTES_IN_MS = 120_000;
@Injectable() @Injectable()
export class WorkspaceDatasourceFactory { export class WorkspaceDatasourceFactory {
@ -58,6 +58,28 @@ export class WorkspaceDatasourceFactory {
private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService, private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService,
) {} ) {}
private async conditionalDestroyDataSource(
dataSource: WorkspaceDataSource,
): Promise<void> {
const isPoolSharingEnabled = this.twentyConfigService.get(
'PG_ENABLE_POOL_SHARING',
);
if (isPoolSharingEnabled) {
this.logger.debug(
`PromiseMemoizer Event: A WorkspaceDataSource (using shared pool) is being cleared. Actual pool closure managed by PgPoolSharedService. Not calling dataSource.destroy().`,
);
// We should NOT call dataSource.destroy() here, because that would end
// the shared pool, potentially affecting other active users of that pool.
// The PgPoolSharedService is responsible for the lifecycle of shared pools.
} else {
this.logger.debug(
`PromiseMemoizer Event: A WorkspaceDataSource (using dedicated pool) is being cleared. Calling safelyDestroyDataSource.`,
);
await this.safelyDestroyDataSource(dataSource);
}
}
private async safelyDestroyDataSource( private async safelyDestroyDataSource(
dataSource: WorkspaceDataSource, dataSource: WorkspaceDataSource,
): Promise<void> { ): Promise<void> {
@ -208,7 +230,8 @@ export class WorkspaceDatasourceFactory {
// https://node-postgres.com/apis/pool // https://node-postgres.com/apis/pool
// TypeORM doesn't allow sharing connection pools between data sources // TypeORM doesn't allow sharing connection pools between data sources
// So we keep a small pool open for longer if connection pooling patch isn't enabled // So we keep a small pool open for longer if connection pooling patch isn't enabled
idleTimeoutMillis: ONE_HOUR_IN_MS, // TODO: Probably not needed anymore when connection pooling patch is enabled
idleTimeoutMillis: TWENTY_MINUTES_IN_MS,
max: 4, max: 4,
allowExitOnIdle: true, allowExitOnIdle: true,
}, },
@ -223,9 +246,7 @@ export class WorkspaceDatasourceFactory {
return workspaceDataSource; return workspaceDataSource;
}, },
async (dataSource) => { this.conditionalDestroyDataSource.bind(this),
await this.safelyDestroyDataSource(dataSource);
},
); );
if (!workspaceDataSource) { if (!workspaceDataSource) {
@ -377,9 +398,7 @@ export class WorkspaceDatasourceFactory {
try { try {
await this.promiseMemoizer.clearKeys( await this.promiseMemoizer.clearKeys(
`${workspaceId}-`, `${workspaceId}-`,
async (dataSource) => { this.conditionalDestroyDataSource.bind(this),
await this.safelyDestroyDataSource(dataSource);
},
); );
} catch (error) { } catch (error) {
// Log and swallow any errors during cleanup to prevent crashes // Log and swallow any errors during cleanup to prevent crashes

View File

@ -20,11 +20,7 @@ interface PoolWithEndTracker extends Pool {
} }
interface ExtendedPoolConfig extends PoolConfig { interface ExtendedPoolConfig extends PoolConfig {
extra?: { allowExitOnIdle?: boolean;
allowExitOnIdle?: boolean;
idleTimeoutMillis?: number;
[key: string]: unknown;
};
} }
interface PoolInternalStats { interface PoolInternalStats {
@ -367,10 +363,9 @@ export class PgPoolSharedService {
poolConfig.idleTimeoutMillis = idleTimeoutMs; poolConfig.idleTimeoutMillis = idleTimeoutMs;
} }
if (!poolConfig.extra) { if (allowExitOnIdle) {
poolConfig.extra = {}; poolConfig.allowExitOnIdle = allowExitOnIdle;
} }
poolConfig.extra.allowExitOnIdle = allowExitOnIdle;
const key = buildPoolKey(poolConfig); const key = buildPoolKey(poolConfig);
const existing = poolsMap.get(key); const existing = poolsMap.get(key);