mirror of
https://github.com/hoornet/vega.git
synced 2026-06-19 19:26:11 -07:00
Fix feed OOM: lazy image loading, inView gating, WebKit memory tuning
- NoteContent: remove ImageGrid from inline mode — images now only load when note is inView (via mediaOnly), stopping runaway scrolling leak - NoteCard: content-visibility:auto to skip layout/paint for off-screen cards; inView-gated media, NoteActions, NIP-05 verification - useInView: new IntersectionObserver hook with 300px rootMargin - useProfile: MAX_PROFILE_CONCURRENT=8 throttle with fetch queue - useReplyCount/useZapCount/useReactions: enabled param, throttled queues - feed.ts: MAX_FEED_SIZE 200→30, live sub disabled (pendingNotes pattern), 250ms batch debounce on live events - core.ts: MAX_CONCURRENT_FETCHES=25 global NDK cap, fetchWithTimeout uses subscribe+stop instead of fetchEvents (no zombie subscriptions) - lib.rs: HardwareAccelerationPolicy::Never + CacheModel::DocumentViewer - main.rs: WEBKIT_DISABLE_COMPOSITING_MODE=1 for Linux - relay/db.rs: TTL eviction + 5000 event cap - feedDiagnostics.ts: file-flushing diag log survives crashes
This commit is contained in:
@@ -441,13 +441,20 @@ pub fn run() {
|
||||
{
|
||||
let main_window = app.get_webview_window("main").unwrap();
|
||||
main_window.with_webview(|webview| {
|
||||
use webkit2gtk::{SettingsExt, WebViewExt};
|
||||
use webkit2gtk::{CacheModel, SettingsExt, WebContextExt, WebViewExt};
|
||||
let wv = webview.inner();
|
||||
if let Some(settings) = wv.settings() {
|
||||
settings.set_hardware_acceleration_policy(
|
||||
webkit2gtk::HardwareAccelerationPolicy::Never,
|
||||
);
|
||||
}
|
||||
// Minimize WebKit's in-memory content cache (decoded images, scripts, etc.)
|
||||
// Default is WebBrowser which caches aggressively. DocumentViewer is the
|
||||
// minimum: no back/forward page cache, smallest memory footprint.
|
||||
// This is safe for Vega — it's a single-page app, never navigates between pages.
|
||||
if let Some(ctx) = wv.context() {
|
||||
ctx.set_cache_model(CacheModel::DocumentViewer);
|
||||
}
|
||||
}).ok();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ fn main() {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
// Required on Linux with large RAM/swap: WebKitGTK compositor pre-allocates
|
||||
// ~25% of total virtual memory (RAM+swap) for its tile cache. On a 14GB RAM +
|
||||
// 19GB swap system this is ~4 GB, filling all RAM and freezing the machine.
|
||||
// Software rendering is slower but memory-safe. Fix: reduce swap or implement
|
||||
// virtual scrolling (fewer compositor layers).
|
||||
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
|
||||
}
|
||||
|
||||
vega_lib::run()
|
||||
|
||||
@@ -3,6 +3,14 @@ use crate::relay::filter::Filter;
|
||||
use rusqlite::{params, Connection};
|
||||
use std::path::Path;
|
||||
|
||||
/// Keep at most this many text notes (kind 1) in the local relay cache.
|
||||
/// Older notes beyond this limit are evicted on startup to bound memory usage.
|
||||
const MAX_KIND1_EVENTS: usize = 5_000;
|
||||
|
||||
/// Delete text notes (kind 1) older than this many seconds on startup.
|
||||
/// 7 days — remote relays have anything older.
|
||||
const KIND1_TTL_SECS: i64 = 7 * 24 * 3600;
|
||||
|
||||
pub fn open_relay_db(data_dir: &Path) -> rusqlite::Result<Connection> {
|
||||
std::fs::create_dir_all(data_dir).ok();
|
||||
let path = data_dir.join("relay.db");
|
||||
@@ -34,9 +42,53 @@ pub fn open_relay_db(data_dir: &Path) -> rusqlite::Result<Connection> {
|
||||
CREATE INDEX IF NOT EXISTS idx_tags_name_value ON event_tags(tag_name, tag_value);
|
||||
CREATE INDEX IF NOT EXISTS idx_tags_event ON event_tags(event_id);",
|
||||
)?;
|
||||
|
||||
evict_old_events(&conn)?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
/// Remove stale text notes on startup to keep the relay cache bounded.
|
||||
///
|
||||
/// Two passes:
|
||||
/// 1. Delete all kind-1 events older than KIND1_TTL_SECS (7 days).
|
||||
/// 2. If more than MAX_KIND1_EVENTS remain, delete the oldest ones beyond that cap.
|
||||
///
|
||||
/// Other kinds (profiles, contact lists, etc.) are not evicted — they are
|
||||
/// replaceable/parameterized-replaceable and stay small by design.
|
||||
fn evict_old_events(conn: &Connection) -> rusqlite::Result<()> {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.unwrap_or(0);
|
||||
|
||||
// Pass 1: TTL — delete kind-1 events older than 7 days
|
||||
let cutoff = now - KIND1_TTL_SECS;
|
||||
conn.execute(
|
||||
"DELETE FROM events WHERE kind = 1 AND created_at < ?1",
|
||||
params![cutoff],
|
||||
)?;
|
||||
|
||||
// Pass 2: count cap — keep only the most recent MAX_KIND1_EVENTS kind-1 events
|
||||
let count: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM events WHERE kind = 1",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
if count > MAX_KIND1_EVENTS as i64 {
|
||||
conn.execute(
|
||||
"DELETE FROM events WHERE kind = 1 AND id NOT IN (
|
||||
SELECT id FROM events WHERE kind = 1
|
||||
ORDER BY created_at DESC LIMIT ?1
|
||||
)",
|
||||
params![MAX_KIND1_EVENTS as i64],
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store an event. Returns true if the event was newly inserted, false if it already existed.
|
||||
/// Handles replaceable (kind 0/3/10000-19999) and parameterized replaceable (30000-39999) events.
|
||||
pub fn store_event(conn: &Connection, event: &Event, raw: &str) -> rusqlite::Result<bool> {
|
||||
|
||||
Reference in New Issue
Block a user