Fix memory leak caused by missing TTL in cache (#2044)
This commit is contained in:
@@ -30,7 +30,7 @@ export function makeLibSignalRepository(
|
|||||||
|
|
||||||
const parsedKeys = auth.keys as SignalKeyStoreWithTransaction
|
const parsedKeys = auth.keys as SignalKeyStoreWithTransaction
|
||||||
const migratedSessionCache = new LRUCache<string, true>({
|
const migratedSessionCache = new LRUCache<string, true>({
|
||||||
ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
|
ttl: 3 * 24 * 60 * 60 * 1000, // 7 days
|
||||||
ttlAutopurge: true,
|
ttlAutopurge: true,
|
||||||
updateAgeOnGet: true
|
updateAgeOnGet: true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { isHostedPnUser, isLidUser, isPnUser, jidDecode, jidNormalizedUser, WAJI
|
|||||||
|
|
||||||
export class LIDMappingStore {
|
export class LIDMappingStore {
|
||||||
private readonly mappingCache = new LRUCache<string, string>({
|
private readonly mappingCache = new LRUCache<string, string>({
|
||||||
ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
|
ttl: 3 * 24 * 60 * 60 * 1000, // 7 days
|
||||||
ttlAutopurge: true,
|
ttlAutopurge: true,
|
||||||
updateAgeOnGet: true
|
updateAgeOnGet: true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import type { ILogger } from './logger'
|
|||||||
/** Number of sent messages to cache in memory for handling retry receipts */
|
/** Number of sent messages to cache in memory for handling retry receipts */
|
||||||
const RECENT_MESSAGES_SIZE = 512
|
const RECENT_MESSAGES_SIZE = 512
|
||||||
|
|
||||||
|
const MESSAGE_KEY_SEPARATOR = '\u0000'
|
||||||
|
|
||||||
/** Timeout for session recreation - 1 hour */
|
/** Timeout for session recreation - 1 hour */
|
||||||
const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000 // 1 hour in milliseconds
|
const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000 // 1 hour in milliseconds
|
||||||
const PHONE_REQUEST_DELAY = 3000
|
const PHONE_REQUEST_DELAY = 3000
|
||||||
@@ -26,9 +28,7 @@ export interface RetryCounter {
|
|||||||
[messageId: string]: number
|
[messageId: string]: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingPhoneRequest {
|
export type PendingPhoneRequest = Record<string, ReturnType<typeof setTimeout>>
|
||||||
[messageId: string]: NodeJS.Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RetryStatistics {
|
export interface RetryStatistics {
|
||||||
totalRetries: number
|
totalRetries: number
|
||||||
@@ -41,8 +41,18 @@ export interface RetryStatistics {
|
|||||||
|
|
||||||
export class MessageRetryManager {
|
export class MessageRetryManager {
|
||||||
private recentMessagesMap = new LRUCache<string, RecentMessage>({
|
private recentMessagesMap = new LRUCache<string, RecentMessage>({
|
||||||
max: RECENT_MESSAGES_SIZE
|
max: RECENT_MESSAGES_SIZE,
|
||||||
|
ttl: 5 * 60 * 1000,
|
||||||
|
ttlAutopurge: true,
|
||||||
|
dispose: (_value: RecentMessage, key: string) => {
|
||||||
|
const separatorIndex = key.lastIndexOf(MESSAGE_KEY_SEPARATOR)
|
||||||
|
if (separatorIndex > -1) {
|
||||||
|
const messageId = key.slice(separatorIndex + MESSAGE_KEY_SEPARATOR.length)
|
||||||
|
this.messageKeyIndex.delete(messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
private messageKeyIndex = new Map<string, string>()
|
||||||
private sessionRecreateHistory = new LRUCache<string, number>({
|
private sessionRecreateHistory = new LRUCache<string, number>({
|
||||||
ttl: RECREATE_SESSION_TIMEOUT * 2,
|
ttl: RECREATE_SESSION_TIMEOUT * 2,
|
||||||
ttlAutopurge: true
|
ttlAutopurge: true
|
||||||
@@ -82,6 +92,7 @@ export class MessageRetryManager {
|
|||||||
message,
|
message,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
})
|
})
|
||||||
|
this.messageKeyIndex.set(id, keyStr)
|
||||||
|
|
||||||
this.logger.debug(`Added message to retry cache: ${to}/${id}`)
|
this.logger.debug(`Added message to retry cache: ${to}/${id}`)
|
||||||
}
|
}
|
||||||
@@ -161,6 +172,7 @@ export class MessageRetryManager {
|
|||||||
// Clean up retry counter for successful message
|
// Clean up retry counter for successful message
|
||||||
this.retryCounters.delete(messageId)
|
this.retryCounters.delete(messageId)
|
||||||
this.cancelPendingPhoneRequest(messageId)
|
this.cancelPendingPhoneRequest(messageId)
|
||||||
|
this.removeRecentMessage(messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,6 +181,8 @@ export class MessageRetryManager {
|
|||||||
markRetryFailed(messageId: string): void {
|
markRetryFailed(messageId: string): void {
|
||||||
this.statistics.failedRetries++
|
this.statistics.failedRetries++
|
||||||
this.retryCounters.delete(messageId)
|
this.retryCounters.delete(messageId)
|
||||||
|
this.cancelPendingPhoneRequest(messageId)
|
||||||
|
this.removeRecentMessage(messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,6 +214,16 @@ export class MessageRetryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private keyToString(key: RecentMessageKey): string {
|
private keyToString(key: RecentMessageKey): string {
|
||||||
return `${key.to}:${key.id}`
|
return `${key.to}${MESSAGE_KEY_SEPARATOR}${key.id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeRecentMessage(messageId: string): void {
|
||||||
|
const keyStr = this.messageKeyIndex.get(messageId)
|
||||||
|
if (!keyStr) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recentMessagesMap.delete(keyStr)
|
||||||
|
this.messageKeyIndex.delete(messageId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user