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:
@ -40,7 +40,7 @@ type CacheResult<T, U> = {
|
||||
data: U;
|
||||
};
|
||||
|
||||
const ONE_HOUR_IN_MS = 3600_000;
|
||||
const TWENTY_MINUTES_IN_MS = 120_000;
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceDatasourceFactory {
|
||||
@ -58,6 +58,28 @@ export class WorkspaceDatasourceFactory {
|
||||
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(
|
||||
dataSource: WorkspaceDataSource,
|
||||
): Promise<void> {
|
||||
@ -208,7 +230,8 @@ export class WorkspaceDatasourceFactory {
|
||||
// https://node-postgres.com/apis/pool
|
||||
// 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
|
||||
idleTimeoutMillis: ONE_HOUR_IN_MS,
|
||||
// TODO: Probably not needed anymore when connection pooling patch is enabled
|
||||
idleTimeoutMillis: TWENTY_MINUTES_IN_MS,
|
||||
max: 4,
|
||||
allowExitOnIdle: true,
|
||||
},
|
||||
@ -223,9 +246,7 @@ export class WorkspaceDatasourceFactory {
|
||||
|
||||
return workspaceDataSource;
|
||||
},
|
||||
async (dataSource) => {
|
||||
await this.safelyDestroyDataSource(dataSource);
|
||||
},
|
||||
this.conditionalDestroyDataSource.bind(this),
|
||||
);
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
@ -377,9 +398,7 @@ export class WorkspaceDatasourceFactory {
|
||||
try {
|
||||
await this.promiseMemoizer.clearKeys(
|
||||
`${workspaceId}-`,
|
||||
async (dataSource) => {
|
||||
await this.safelyDestroyDataSource(dataSource);
|
||||
},
|
||||
this.conditionalDestroyDataSource.bind(this),
|
||||
);
|
||||
} catch (error) {
|
||||
// Log and swallow any errors during cleanup to prevent crashes
|
||||
|
||||
@ -20,11 +20,7 @@ interface PoolWithEndTracker extends Pool {
|
||||
}
|
||||
|
||||
interface ExtendedPoolConfig extends PoolConfig {
|
||||
extra?: {
|
||||
allowExitOnIdle?: boolean;
|
||||
idleTimeoutMillis?: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
allowExitOnIdle?: boolean;
|
||||
}
|
||||
|
||||
interface PoolInternalStats {
|
||||
@ -367,10 +363,9 @@ export class PgPoolSharedService {
|
||||
poolConfig.idleTimeoutMillis = idleTimeoutMs;
|
||||
}
|
||||
|
||||
if (!poolConfig.extra) {
|
||||
poolConfig.extra = {};
|
||||
if (allowExitOnIdle) {
|
||||
poolConfig.allowExitOnIdle = allowExitOnIdle;
|
||||
}
|
||||
poolConfig.extra.allowExitOnIdle = allowExitOnIdle;
|
||||
|
||||
const key = buildPoolKey(poolConfig);
|
||||
const existing = poolsMap.get(key);
|
||||
|
||||
Reference in New Issue
Block a user