mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 21:37:28 +00:00
fix(discord): add TTL and LRU eviction to thread starter cache
Fixes #5260 The DISCORD_THREAD_STARTER_CACHE Map was growing unbounded during long-running gateway sessions, causing memory exhaustion. This fix adds: - 5-minute TTL expiry (thread starters rarely change) - Max 500 entries with LRU eviction - Same caching pattern used by Slack's thread resolver The implementation mirrors src/slack/monitor/thread-resolution.ts which already handles this correctly.
This commit is contained in:
@@ -29,12 +29,54 @@ type DiscordThreadParentInfo = {
|
||||
type?: ChannelType;
|
||||
};
|
||||
|
||||
const DISCORD_THREAD_STARTER_CACHE = new Map<string, DiscordThreadStarter>();
|
||||
// Cache entry with timestamp for TTL-based eviction
|
||||
type DiscordThreadStarterCacheEntry = {
|
||||
value: DiscordThreadStarter;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
// Cache configuration: 5 minute TTL (thread starters rarely change), max 500 entries
|
||||
const DISCORD_THREAD_STARTER_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
const DISCORD_THREAD_STARTER_CACHE_MAX = 500;
|
||||
|
||||
const DISCORD_THREAD_STARTER_CACHE = new Map<string, DiscordThreadStarterCacheEntry>();
|
||||
|
||||
export function __resetDiscordThreadStarterCacheForTest() {
|
||||
DISCORD_THREAD_STARTER_CACHE.clear();
|
||||
}
|
||||
|
||||
// Get cached entry with TTL check, refresh LRU position on hit
|
||||
function getCachedThreadStarter(key: string, now: number): DiscordThreadStarter | undefined {
|
||||
const entry = DISCORD_THREAD_STARTER_CACHE.get(key);
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
// Check TTL expiry
|
||||
if (now - entry.updatedAt > DISCORD_THREAD_STARTER_CACHE_TTL_MS) {
|
||||
DISCORD_THREAD_STARTER_CACHE.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
// Refresh LRU position by re-inserting (Map maintains insertion order)
|
||||
DISCORD_THREAD_STARTER_CACHE.delete(key);
|
||||
DISCORD_THREAD_STARTER_CACHE.set(key, { ...entry, updatedAt: now });
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
// Set cached entry with LRU eviction when max size exceeded
|
||||
function setCachedThreadStarter(key: string, value: DiscordThreadStarter, now: number): void {
|
||||
// Remove existing entry first (to update LRU position)
|
||||
DISCORD_THREAD_STARTER_CACHE.delete(key);
|
||||
DISCORD_THREAD_STARTER_CACHE.set(key, { value, updatedAt: now });
|
||||
// Evict oldest entries (first in Map) when over max size
|
||||
while (DISCORD_THREAD_STARTER_CACHE.size > DISCORD_THREAD_STARTER_CACHE_MAX) {
|
||||
const oldestKey = DISCORD_THREAD_STARTER_CACHE.keys().next().value;
|
||||
if (!oldestKey) {
|
||||
break;
|
||||
}
|
||||
DISCORD_THREAD_STARTER_CACHE.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
|
||||
function isDiscordThreadType(type: ChannelType | undefined): boolean {
|
||||
return (
|
||||
type === ChannelType.PublicThread ||
|
||||
@@ -100,7 +142,8 @@ export async function resolveDiscordThreadStarter(params: {
|
||||
resolveTimestampMs: (value?: string | null) => number | undefined;
|
||||
}): Promise<DiscordThreadStarter | null> {
|
||||
const cacheKey = params.channel.id;
|
||||
const cached = DISCORD_THREAD_STARTER_CACHE.get(cacheKey);
|
||||
const now = Date.now();
|
||||
const cached = getCachedThreadStarter(cacheKey, now);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
@@ -146,7 +189,7 @@ export async function resolveDiscordThreadStarter(params: {
|
||||
author,
|
||||
timestamp: timestamp ?? undefined,
|
||||
};
|
||||
DISCORD_THREAD_STARTER_CACHE.set(cacheKey, payload);
|
||||
setCachedThreadStarter(cacheKey, payload, Date.now());
|
||||
return payload;
|
||||
} catch {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user