From bc4c72d6a090badd22d884e5af4e2cfc036f5490 Mon Sep 17 00:00:00 2001 From: Jure <44338+hoornet@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:55:06 +0200 Subject: [PATCH] Fix Linux OOM: disable Blossom SHA-256 URL auto-detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 214c42b (v0.12.6) added auto-detection of content-addressed Blossom URLs (64-hex SHA-256 paths) as elements. Blossom is widespread in modern Nostr feeds — every feed page started rendering 3-5x more elements. Combined with WebKitGTK's weak decoded- bitmap eviction, feed scrolling grew the WebKit web process to 8-12 GB and triggered WebKit's self-kill threshold with: Unable to shrink memory footprint of process (9022 MB) below the kill threshold (8192 MB). Killed Disable BLOSSOM_URL_REGEX in parseContent(). Real Blossom images shared via standard upload flows (with proper extensions) still render. Proper reintroduction (HEAD request + Content-Type validation, or known-server whitelist) planned for v0.12.9. Also restore feed depth caps to pre-crisis values now that memory is under control: - MAX_FEED_SIZE 30 → 200 (v0.12.6 baseline) - fetchFollowFeed limit 30 → 100 - fetchGlobalFeed fetch 80 → 100 - Following tab slice 30 → 100 The earlier 30-caps were themselves OOM firefighting that shipped in v0.12.7 and were no longer needed. Memory verified 2026-04-16: oscillates 1.1-1.6 GB across all tabs (Global / Following / Trending / Media / profile / thread) under heavy use with embedded relay enabled. No crashes. Elastic cache behaviour rather than monotonic leak — memory spikes briefly on content loads and reclaims within seconds. See private_docs/WEBKIT_OOM_INVESTIGATION.md for the full investigation (4 days of chasing symptoms before finding the one-line regex as the real cause). --- .github/workflows/release.yml | 8 ++++---- src-tauri/Cargo.lock | 2 +- src/components/feed/Feed.tsx | 2 +- src/lib/nostr/notes.ts | 2 +- src/lib/parsing.ts | 9 ++++++--- src/stores/feed.ts | 4 ++-- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c572785..216dbac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,10 +71,10 @@ jobs: ### v0.12.8 — Fix Linux OOM crash - - **Fix Linux memory crash** — Vega no longer crashes or freezes the OS on Linux; memory now plateaus at ~950MB during heavy use instead of spiking to 32GB+ - - **Following feed capped** — following feed was rendering up to 80 notes (vs. 30 for global), causing 4GB+ spikes on media-heavy accounts; now capped at 30 - - **WebKit rendering fix** — switched to `WEBKIT_FORCE_SOFTWARE_RENDERING=1` which fixes the blank window issue on Hyprland/Wayland while keeping memory low - - **Notification fetch dedup** — `fetchNotifications` was firing 3× in the first 8 seconds of login; now fires once and the first background poll is delayed to 90s + - **Fix Linux memory crash** — Vega no longer crashes on Linux. Memory now oscillates at ~1.1–1.6 GB during heavy use instead of climbing to 8–12 GB. Root cause traced to v0.12.6: the Blossom SHA-256 URL auto-detection regex caused 3–5× more `` elements per feed page, which combined with WebKitGTK's weak bitmap eviction pushed the WebKit process past its 8 GB self-kill threshold. Blossom auto-detection is temporarily disabled pending proper validation in v0.12.9. + - **Feed depths restored** — the v0.12.7 OOM firefighting that slashed follow/global feed limits to 30 is reverted; follow feed now fetches 100 and global caches up to 200, matching pre-crisis v0.12.6 behavior. + - **WebKit rendering fix** — `WEBKIT_FORCE_SOFTWARE_RENDERING=1` keeps the Wayland compositor path intact on Hyprland. + - **Notification fetch dedup** — `fetchNotifications` was firing 3× in the first 8 seconds of login; now fires once and the first background poll is delayed to 90s. ### v0.12.7 — Upload Fixes diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 64abe15..96dbcc1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5320,7 +5320,7 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vega" -version = "0.12.7" +version = "0.12.8" dependencies = [ "futures-util", "hex", diff --git a/src/components/feed/Feed.tsx b/src/components/feed/Feed.tsx index 2d7ea91..35a4cea 100644 --- a/src/components/feed/Feed.tsx +++ b/src/components/feed/Feed.tsx @@ -89,7 +89,7 @@ export function Feed() { try { await ensureConnected(); const events = await diagWrapFetch("follow_fetch", () => fetchFollowFeed(follows)); - setFollowNotes(events.slice(0, 30)); + setFollowNotes(events.slice(0, 100)); const prev = useFeedStore.getState().lastUpdated; useFeedStore.setState({ lastUpdated: { ...prev, following: Date.now() } }); } finally { diff --git a/src/lib/nostr/notes.ts b/src/lib/nostr/notes.ts index a09a839..bdb0062 100644 --- a/src/lib/nostr/notes.ts +++ b/src/lib/nostr/notes.ts @@ -20,7 +20,7 @@ export async function fetchMediaFeed(limit: number = 500): Promise { return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0)); } -export async function fetchFollowFeed(pubkeys: string[], limit = 30): Promise { +export async function fetchFollowFeed(pubkeys: string[], limit = 100): Promise { if (pubkeys.length === 0) return []; const instance = getNDK(); const since = Math.floor(Date.now() / 1000) - 24 * 3600; // last 24h for follows diff --git a/src/lib/parsing.ts b/src/lib/parsing.ts index aeae4b2..38b333f 100644 --- a/src/lib/parsing.ts +++ b/src/lib/parsing.ts @@ -3,8 +3,11 @@ import { nip19 } from "@nostr-dev-kit/ndk"; // Regex patterns const URL_REGEX = /https?:\/\/[^\s<>"')\]]+/g; const IMAGE_EXTENSIONS = /\.(jpg|jpeg|jpg2|jp2|jpe|png|gif|webp|svg|avif|bmp|tiff?)(\?[^\s]*)?$/i; -// Blossom / NIP-96 content-addressed URLs: SHA-256 hash (64 hex) as filename, any extension -const BLOSSOM_URL_REGEX = /\/[0-9a-f]{64}(\.[a-z0-9]{0,5})?$/i; +// Blossom / NIP-96 content-addressed URLs: SHA-256 hash (64 hex) as filename. +// DISABLED pending OOM investigation — rendering these as roughly doubled +// decoded-bitmap pressure on WebKitGTK and is suspected to have pushed the feed +// past the kill threshold starting in v0.12.6. +// const BLOSSOM_URL_REGEX = /\/[0-9a-f]{64}(\.[a-z0-9]{0,5})?$/i; const VIDEO_EXTENSIONS = /\.(mp4|webm|mov|ogg|m4v|avi)(\?[^\s]*)?$/i; const AUDIO_EXTENSIONS = /\.(mp3|wav|flac|aac|m4a|opus|ogg)(\?[^\s]*)?$/i; const YOUTUBE_REGEX = /(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/; @@ -39,7 +42,7 @@ export function parseContent(content: string): ContentSegment[] { // Clean trailing punctuation that's likely not part of the URL const cleaned = url.replace(/[.,;:!?)]+$/, ""); - if (IMAGE_EXTENSIONS.test(cleaned) || BLOSSOM_URL_REGEX.test(cleaned)) { + if (IMAGE_EXTENSIONS.test(cleaned)) { allMatches.push({ index: match.index, length: cleaned.length, diff --git a/src/stores/feed.ts b/src/stores/feed.ts index 2e89732..8856c17 100644 --- a/src/stores/feed.ts +++ b/src/stores/feed.ts @@ -11,7 +11,7 @@ import { debug } from "../lib/debug"; const TRENDING_CACHE_KEY = "wrystr_trending_cache"; const TRENDING_TTL = 10 * 60 * 1000; // 10 minutes -const MAX_FEED_SIZE = 30; +const MAX_FEED_SIZE = 200; // Live subscription handle — persists across store calls let liveSub: NDKSubscription | null = null; @@ -187,7 +187,7 @@ export const useFeedStore = create((set, get) => ({ set({ loading: true, error: null }); try { await ensureConnected(); - const fresh = await diagWrapFetch("global_fetch", () => fetchGlobalFeed(80)); + const fresh = await diagWrapFetch("global_fetch", () => fetchGlobalFeed(100)); // Merge with currently displayed notes const freshIds = new Set(fresh.map((n) => n.id));