Skip to content

make vfs hot path local-only#9

Merged
Dhravya merged 2 commits intomainfrom
Prasanna721/local-first-vfs
May 6, 2026
Merged

make vfs hot path local-only#9
Dhravya merged 2 commits intomainfrom
Prasanna721/local-first-vfs

Conversation

@Prasanna721
Copy link
Copy Markdown
Member

@Prasanna721 Prasanna721 commented May 6, 2026

vfs lookup/readdir/readdir_plus awaited pull_documents inline,
putting network latency inside the kernel-bounded syscall path.
under a burst of 120 concurrent mkdirs (webpull's normal behavior)
macos NFS would soft-timeout and the kernel returned ETIMEDOUT.
fix is structural: the syscall must be local-only.

added HydrationScheduler (in-memory, singleflight + bounded
workers + short negative-TTL) and swapped the three hot-path call
sites to enqueue instead of await. delta pull, push queue, smfs
sync are unchanged.

also ships the dirty_since guard on the attach-by-path reconcile
branch. prerequisite: without it, async hydration could clobber a
local write still pending in the push queue.

how I tested:

  • 158 unit tests pass + 8 new (3 hot-path + 5 hydration scheduler)
  • webpull https://supermemory.ai/docs against real fs vs fresh smfs
    mount, both macos/NFS and linux/FUSE: identical tree and identical
    sha256 across all 120 files, 0 mkdir errors, smfs run finished
    faster than the real fs run

lookup/readdir/readdir_plus awaited pull_documents inline. network
latency inside a kernel-bounded syscall. mkdir bursts (webpull) hit
the soft-mount timeout and returned ETIMEDOUT.

new HydrationScheduler enqueues misses and drains them with a
bounded worker pool, dedup by key, short negative-TTL. fix the
dirty_since guard on the attach-by-path reconcile branch first so
async hydration can't clobber pending local writes.

verified parity (real fs vs smfs) on macos/nfs and linux/fuse:
120-page webpull, identical tree + sha256, 0 errors.
@Prasanna721 Prasanna721 requested a review from Dhravya May 6, 2026 02:40
Copy link
Copy Markdown
Contributor

@vorflux vorflux Bot left a comment

Choose a reason for hiding this comment

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

Solid approach -- making the VFS hot path local-only is the right structural fix. The hydration scheduler is clean and well-tested. A few things I spotted below.

if dirty_since > ms {
self.db.set_remote_id(ino, &doc.id);
self.db.push_queue_set_remote_id(filepath, &doc.id);
return Ok(ReconcileOutcome::SkippedDirty);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This push_queue_set_remote_id call takes filepath (a &str derived from doc.filepath), but push_queue_set_remote_id in db.rs matches on the filepath key already in the push queue. If the file was locally created before the remote doc existed, the push queue entry filepath might differ from doc.filepath. Worth verifying this matches the key used in push_queue_upsert when the file was originally enqueued -- otherwise this UPDATE matches zero rows.


Ok(Some(Vec::new()))
let mut names: Vec<String> = Vec::new();
if ino == ROOT_INO && self.api.is_some() && !names.contains(&PROFILE_NAME.to_string()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor: !names.contains(&PROFILE_NAME.to_string()) is always true here since names was just created as Vec::new() on line 1186. Dead code in this branch -- could simplify to just if ino == ROOT_INO && self.api.is_some().

let mut inner = self.inner.lock();
inner
.recent
.retain(|_, ts| now.duration_since(*ts) < self.negative_ttl);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

retain scans the entire recent HashMap on every enqueue. With 2s TTL and 4 workers, n stays small in practice. But consider amortizing (e.g., only retain every Nth call or when recent.len() > threshold) if burst sizes grow.


// Attach remote_id even on skip so the queued push promotes
// Create→Update instead of POSTing a duplicate.
if let Some(dirty_since) = self.db.get_dirty_since(ino) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Subtle ordering concern: if updated_ms is None (parse failure on doc.updated_at) and dirty_since is Some, the if let Some(ms) = updated_ms check fails, we fall through to line 359 and unconditionally set remote_id + rewrite content -- potentially clobbering a local dirty write. Consider: if dirty_since.is_some() && updated_ms.is_none(), return SkippedDirty as a safety net.

Copy link
Copy Markdown
Member

Dhravya commented May 6, 2026

@Prasanna721 - The reviews from Vorflux look legit

@Dhravya Dhravya merged commit 1647952 into main May 6, 2026
4 checks passed
@Prasanna721 Prasanna721 deleted the Prasanna721/local-first-vfs branch May 7, 2026 21:13
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