fix(api): various subtle memory leaks

(and some not so subtle ones, *cough* ReportService *cough*)
This commit is contained in:
Hampus Kraft
2026-02-27 04:50:21 +00:00
parent 848269a4d4
commit 7b1aa6ff2e
8 changed files with 193 additions and 41 deletions

View File

@@ -52,6 +52,7 @@ class IpBanCache {
private kvClient: IKVProvider | null = null;
private kvSubscription: IKVSubscription | null = null;
private subscriberInitialized = false;
private messageHandler: ((channel: string) => void) | null = null;
constructor() {
this.singleIpBans = this.createFamilyMaps();
@@ -78,23 +79,27 @@ class IpBanCache {
const subscription = this.kvClient.duplicate();
this.kvSubscription = subscription;
this.messageHandler = (channel: string) => {
if (channel === IP_BAN_REFRESH_CHANNEL) {
this.refresh().catch((err) => {
this.consecutiveFailures++;
const message = err instanceof Error ? err.message : String(err);
if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
Logger.error({error: message}, 'Failed to refresh IP ban cache after notification');
} else {
Logger.warn({error: message}, 'Failed to refresh IP ban cache after notification');
}
});
}
};
subscription
.connect()
.then(() => subscription.subscribe(IP_BAN_REFRESH_CHANNEL))
.then(() => {
subscription.on('message', (channel) => {
if (channel === IP_BAN_REFRESH_CHANNEL) {
this.refresh().catch((err) => {
this.consecutiveFailures++;
const message = err instanceof Error ? err.message : String(err);
if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
Logger.error({error: message}, 'Failed to refresh IP ban cache after notification');
} else {
Logger.warn({error: message}, 'Failed to refresh IP ban cache after notification');
}
});
}
});
if (this.messageHandler) {
subscription.on('message', this.messageHandler);
}
})
.catch((error) => {
Logger.error({error}, 'Failed to subscribe to IP ban refresh channel');
@@ -203,10 +208,14 @@ class IpBanCache {
}
shutdown(): void {
if (this.kvSubscription && this.messageHandler) {
this.kvSubscription.removeAllListeners('message');
}
if (this.kvSubscription) {
this.kvSubscription.disconnect();
this.kvSubscription = null;
}
this.messageHandler = null;
}
}

View File

@@ -173,6 +173,15 @@ import {createMiddleware} from 'hono/factory';
const errorI18nService = new ErrorI18nService();
let _reportService: ReportService | null = null;
export function shutdownReportService(): void {
if (_reportService) {
_reportService.shutdown();
_reportService = null;
}
}
let _testEmailService: TestEmailService | null = null;
function getTestEmailService(): TestEmailService {
if (!_testEmailService) {
@@ -617,19 +626,21 @@ export const ServiceMiddleware = createMiddleware<HonoEnv>(async (ctx, next) =>
const desktopHandoffService = new DesktopHandoffService(cacheService);
const authRequestService = new AuthRequestService(authService, ssoService, cacheService, desktopHandoffService);
const reportSearchService = getReportSearchService();
const reportService = new ReportService(
reportRepository,
channelRepository,
guildRepository,
userRepository,
inviteRepository,
emailService,
emailDnsValidationService,
snowflakeService,
storageService,
reportSearchService,
);
if (!_reportService) {
_reportService = new ReportService(
reportRepository,
channelRepository,
guildRepository,
userRepository,
inviteRepository,
emailService,
emailDnsValidationService,
snowflakeService,
storageService,
getReportSearchService(),
);
}
const reportService = _reportService;
const reportRequestService = new ReportRequestService(reportService);
const adminService = new AdminService(