mirror of
https://github.com/hoornet/vega.git
synced 2026-06-08 14:11:55 -07:00
Fix Linux OOM: disable Blossom SHA-256 URL auto-detection
Commit 214c42b (v0.12.6) added auto-detection of content-addressed
Blossom URLs (64-hex SHA-256 paths) as <img> elements. Blossom is
widespread in modern Nostr feeds — every feed page started rendering
3-5x more <img> 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).
This commit is contained in:
@@ -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 `<img>` 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
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -5320,7 +5320,7 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vega"
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"hex",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function fetchMediaFeed(limit: number = 500): Promise<NDKEvent[]> {
|
||||
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
|
||||
}
|
||||
|
||||
export async function fetchFollowFeed(pubkeys: string[], limit = 30): Promise<NDKEvent[]> {
|
||||
export async function fetchFollowFeed(pubkeys: string[], limit = 100): Promise<NDKEvent[]> {
|
||||
if (pubkeys.length === 0) return [];
|
||||
const instance = getNDK();
|
||||
const since = Math.floor(Date.now() / 1000) - 24 * 3600; // last 24h for follows
|
||||
|
||||
+6
-3
@@ -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 <img> 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,
|
||||
|
||||
+2
-2
@@ -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<FeedState>((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));
|
||||
|
||||
Reference in New Issue
Block a user