Skip to content

fixed backend live capture storage lag#27

Open
virajshoor wants to merge 1 commit into
b-nnett:mainfrom
virajshoor:fix/backend-live-capture-storage
Open

fixed backend live capture storage lag#27
virajshoor wants to merge 1 commit into
b-nnett:mainfrom
virajshoor:fix/backend-live-capture-storage

Conversation

@virajshoor

Copy link
Copy Markdown

improves local sqlite performance for live capture by enabling wal/busy timeout pragmas, avoiding repeated migration work on hot capture bridge calls, adding capture-path indexes, and centralizing capture queue timing.

@tigercraft4 tigercraft4 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Swift changes are clean and ready to ship. The two Rust files have issues that should be addressed before merge — four inline comments below in priority order.

Comment thread Rust/core/src/store.rs
}

fn configure_read_write_connection(conn: &Connection) -> GooseResult<()> {
conn.execute_batch(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WAL + iOS file protection — highest-severity issue.

journal_mode=WAL is a persistent DB property: once set, the database stays WAL across all future opens, including open_read_only connections. Under WAL, read-only opens still require write access to the -wal and -shm sidecar files.

This app runs with bluetooth-central + location background modes and an overnight guard that writes to SQLite while the device is locked. If the DB sits under default NSFileProtectionComplete, locked-device sidecar writes fail with SQLITE_IOERR or SQLITE_CANTOPEN — turning a performance fix into a write failure in production.

Fix: force NSFileProtectionCompleteUntilFirstUserAuthentication on the DB file (and ideally the sidecars after first WAL open), or confirm in the PR that the file-protection class is already set to a WAL-compatible level.

Comment thread Rust/core/src/store.rs
}

pub fn open_existing_current(path: &Path) -> GooseResult<Self> {
let conn = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open_existing_current schema-completeness gap.

This method gates on user_version == CURRENT_SCHEMA_VERSION, but ensure_*_columns() ALTERs run in migrate() after user_version is set. A DB stamped version 14 by an older binary that predates a later ensure_*_column addition passes this gate, then hits a runtime SQL error on the first column-dependent query — a failure the open-time fallback in open_bridge_store_hot cannot catch (the fallback only triggers on open errors, not later query errors).

Either run ensure_*_columns inside open_existing_current, or document that user_version is a heuristic and that the hot path assumes a prior full open() has already run.

Comment thread Rust/core/src/bridge.rs
}

fn open_bridge_store_hot(database_path: &str) -> GooseResult<GooseStore> {
if database_path.trim().is_empty() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No checkpoint policy — potential storage regression inside a storage-lag fix.

With WAL enabled across all 92+ open_bridge_store call sites (each opening a fresh Connection), WAL auto-checkpoint (default 1000 pages) fires on whichever connection crosses the threshold. Without an explicit checkpoint strategy the -wal file can grow unbounded during long capture or overnight sessions.

Consider adding to configure_read_write_connection:

PRAGMA wal_autocheckpoint = 100;

or scheduling periodic PRAGMA wal_checkpoint(TRUNCATE) from the capture queue.

Comment thread Rust/core/src/bridge.rs
}
let path = Path::new(database_path);
GooseStore::open_existing_current(path).or_else(|_| GooseStore::open(path))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent fallback swallows the real error.

.or_else(|_| GooseStore::open(path)) discards why open_existing_current failed. If it fails for I/O, lock contention, or corruption (not just a stale schema), you run a full migration against a possibly damaged DB with no log. Recommend logging the discarded error:

.or_else(|err| {
    log::warn!("open_existing_current failed ({err}), falling back to full open");
    GooseStore::open(path)
})

Also: the startup ordering assumption is currently implicit — the hot path works correctly only after a full open() (with migrate()) has run at least once. A comment here or in GooseAppModel documenting that dependency would prevent regressions if startup order ever changes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching the error, I believe it is ready now

tigercraft4 referenced this pull request in tigercraft4/goose Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants