Fix thread OOM: cap fetchThreadEvents at 300 events, cap profile cache at 500 entries

This commit is contained in:
Jure
2026-04-13 21:37:41 +02:00
parent 5d59797d5d
commit a87abb6d97
2 changed files with 27 additions and 8 deletions
+14
View File
@@ -2,9 +2,22 @@ import { useEffect, useState } from "react";
import { fetchProfile } from "../lib/nostr";
import { dbLoadProfile, dbSaveProfile } from "../lib/db";
const PROFILE_CACHE_MAX = 500;
const profileCache = new Map<string, any>();
const pendingRequests = new Map<string, Promise<any>>();
function pruneProfileCache() {
if (profileCache.size > PROFILE_CACHE_MAX) {
// Drop oldest entries (Map preserves insertion order)
const toDelete = profileCache.size - PROFILE_CACHE_MAX;
let i = 0;
for (const key of profileCache.keys()) {
if (i++ >= toDelete) break;
profileCache.delete(key);
}
}
}
export function invalidateProfileCache(pubkey: string) {
profileCache.delete(pubkey);
pendingRequests.delete(pubkey);
@@ -25,6 +38,7 @@ export function useProfile(pubkey: string) {
.then((p) => {
const result = p ?? null;
profileCache.set(pubkey, result);
pruneProfileCache();
pendingRequests.delete(pubkey);
if (result) dbSaveProfile(pubkey, JSON.stringify(result));
return result;
+13 -8
View File
@@ -136,22 +136,27 @@ export async function publishQuote(content: string, quotedEvent: NDKEvent): Prom
await note.publish();
}
const THREAD_EVENT_LIMIT = 300; // hard cap to prevent OOM on viral threads
export async function fetchThreadEvents(rootId: string): Promise<NDKEvent[]> {
const instance = getNDK();
// Round-trip 1: all events tagging the root
const directFilter: NDKFilter = { kinds: [NDKKind.Text], "#e": [rootId] };
// Round-trip 1: all events tagging the root (capped)
const directFilter: NDKFilter = { kinds: [NDKKind.Text], "#e": [rootId], limit: THREAD_EVENT_LIMIT };
const directEvents = await fetchWithTimeout(instance, directFilter, THREAD_TIMEOUT);
const allEvents = new Map<string, NDKEvent>();
for (const e of directEvents) allEvents.set(e.id, e);
// Round-trip 2: replies to any event in the thread
const knownIds = Array.from(allEvents.keys());
if (knownIds.length > 0) {
const deepFilter: NDKFilter = { kinds: [NDKKind.Text], "#e": knownIds };
const deepEvents = await fetchWithTimeout(instance, deepFilter, THREAD_TIMEOUT);
for (const e of deepEvents) allEvents.set(e.id, e);
// Round-trip 2: replies to events in the thread — only if round 1 returned < limit
// Skip deep fetch on large threads to avoid OOM
if (allEvents.size < THREAD_EVENT_LIMIT) {
const knownIds = Array.from(allEvents.keys()).slice(0, 50); // cap #e filter size
if (knownIds.length > 0) {
const deepFilter: NDKFilter = { kinds: [NDKKind.Text], "#e": knownIds, limit: THREAD_EVENT_LIMIT - allEvents.size };
const deepEvents = await fetchWithTimeout(instance, deepFilter, THREAD_TIMEOUT);
for (const e of deepEvents) allEvents.set(e.id, e);
}
}
return Array.from(allEvents.values());