fix(#252): close leftover ioredis handles so e2e jest exits cleanly (no forceExit) #254

Open
agent_coder wants to merge 1 commits from fix/252-e2e-open-handles into develop
2 changed files with 31 additions and 19 deletions

View File

@@ -33,6 +33,11 @@ export class CollaborationGateway {
// @ts-ignore
private readonly redisSync: RedisSyncExtension<CollabEventHandlers> | null =
null;
// Source ioredis client that RedisSyncExtension duplicates into its pub/sub
// pair. The extension's onDestroy only disconnects those duplicates, so we
// keep a reference here and disconnect the source ourselves on shutdown
// (otherwise the socket leaks and jest never exits in e2e).
private redisClient: RedisClient | null = null;
private readonly withRedis: boolean;
constructor(
@@ -57,16 +62,17 @@ export class CollaborationGateway {
});
if (this.withRedis) {
this.redisClient = new RedisClient({
host: this.redisConfig.host,
port: this.redisConfig.port,
password: this.redisConfig.password,
db: this.redisConfig.db,
family: this.redisConfig.family,
retryStrategy: createRetryStrategy(),
});
// @ts-ignore
this.redisSync = new RedisSyncExtension({
redis: new RedisClient({
host: this.redisConfig.host,
port: this.redisConfig.port,
password: this.redisConfig.password,
db: this.redisConfig.db,
family: this.redisConfig.family,
retryStrategy: createRetryStrategy(),
}),
redis: this.redisClient,
serverId: `collab-${os?.hostname()}-${nanoid(10)}`,
prefix: 'collab',
pack,
@@ -184,5 +190,10 @@ export class CollaborationGateway {
});
await this.hocuspocus.hooks('onDestroy', { instance: this.hocuspocus });
// RedisSyncExtension.onDestroy (run via the hook above) disconnects only the
// duplicated pub/sub clients; the source client created here is ours to close.
this.redisClient?.disconnect();
this.redisClient = null;
}
}

View File

@@ -10,7 +10,6 @@ import {
PAGE_TEMPLATE_THROTTLER,
PUBLIC_SHARE_AI_THROTTLER,
} from './throttler-names';
import Redis from 'ioredis';
@Module({
imports: [
@@ -32,16 +31,18 @@ import Redis from 'ioredis';
{ name: PUBLIC_SHARE_AI_THROTTLER, ttl: 60_000, limit: 5 },
],
errorMessage: 'Too many requests',
storage: new ThrottlerStorageRedisService(
new Redis({
host: redisConfig.host,
port: redisConfig.port,
password: redisConfig.password,
db: redisConfig.db,
family: redisConfig.family,
keyPrefix: 'throttle:',
}),
),
// Pass ioredis options (not a pre-built Redis instance) so
// ThrottlerStorageRedisService owns the connection and disconnects it
// in its onModuleDestroy. Passing an instance leaves disconnectRequired
// false, so the socket would leak on shutdown (e2e jest never exits).
storage: new ThrottlerStorageRedisService({
host: redisConfig.host,
port: redisConfig.port,
password: redisConfig.password,
db: redisConfig.db,
family: redisConfig.family,
keyPrefix: 'throttle:',
}),
};
},
inject: [EnvironmentService],