mirror of
https://github.com/hoornet/vega.git
synced 2026-06-08 14:11:55 -07:00
Fix thread OOM: cap fetchThreadEvents at 300 events, cap profile cache at 500 entries
This commit is contained in:
@@ -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
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user