Repository navigation
v1.7.0
[1.7.0] — 2026-08-16
Performance and robustness pass over the mount, from the architecture audit in
docs/plans/audit-2026-08-15-perf-locks.md. Schema: 25.
Added
- Sequential-read prefetch (
Core::prefetch_ahead): per-file sequential detection, depth
doubling 2→8, reset to 0 after a seek, skipping blocks already cached, and bounded by its
own permit budget so it can never queue ahead of a demand read. - Single-flight block fetching (
BlockFlight) keyed on(uid, mtime, size, block), so a
demand read, kernel read-ahead and prefetch of the same block share one download+decrypt. - In-memory block LRU (
BlockRing) in front of both block paths; a block read off disk is
promoted into it, so the following ~31 kernel reads of that 4 MiB block cost nothing. - Read-only SQLite connection pool (
ReadPool/Db::read) with everySELECT-only method
routed at it, so a read no longer waits on the write connection. nodes.pathpersisted and backfilled, replacing the recursivepath_ofwalk.- Single-writer
flockon the database; aSQLITE_CORRUPT/NOTADBfile is quarantined as
cache.db.corrupt-<ts>(WAL sidecars too) and rebuilt rather than failing the mount. VACUUMand the deepintegrity_checknow ack immediately and run as background jobs;
pdfs doctorpolls for the verdict.- Staging reconcile at mount: staged blobs no queued operation refers to are re-queued, or
named in the log when they cannot be addressed. - Queue/staging visibility —
Statusgainedparked_uploads,failing_ops,
failing_error,staged_bytes,staged_oldest_secs;pdfs statusprints them when
non-zero. statfsimplemented against the account quota; FUSEinitnegotiatesmax_readahead,
max_background,PARALLEL_DIROPSandCACHE_SYMLINKS;blksizeraised to 1 MiB.
Changed
Stateno longer writes to SQLite under the inode lock — mutations queue into an outbox
applied after the lock is released, so interning a 5000-child listing no longer commits a
transaction with the whole mount frozen.- Re-listing an unchanged folder no longer touches its subtree; when a subtree really moved,
one recursive walk carries both the new path and the inherited trashed flag. release(2)no longer gap-fills from the network: it stages an incomplete blob from local
caches only and the drain thread finishes it off the dispatch loop.- A partial write over an undrained one is merged instead of refused.
- Size-upgrade waiters park on a queue instead of occupying a worker thread, capped at 8
folders in flight; past the capstatanswers provisionally. - Block-cache disk writes moved off the read's critical path; no more fsync per cached block.
- Cache LRU touches buffered in memory and flushed in one transaction.
- Search: sync-folder list and pin lookups hoisted out of the per-hit loop.
- SQLite tuned (
cache_size,mmap_size,temp_store=MEMORY,wal_autocheckpoint) and
three new indexes added. - Mount registry lock released before FUSE teardown/join; kernel notifications batched under
the state lock and flushed after it.
Fixed
- Data loss:
ENOSPCis now propagated fromwrite/fsync/staging instead of being
swallowed, with emergency eviction wired up and scratch marked durable before any staging
error; a failedrecord_pending_writefailsreleaserather than dropping the write. - Data loss: the conflict sweep no longer discards queued operations after the trash
round trip. - Data loss:
rescue_scratchmoves a blob with an unreadable sidecar torecovery/
unaccompanied instead of fabricating a sidecar for it. - Own-sealed revisions are persisted (7-day TTL), so a restart no longer reopens the
transient-download fork window fixed in 1.1.0. - Depth-capped
is_pinned(cycle-safe), daemon write timeout, and every client-supplied
limitclamped. recover_fsynced_writesno longer scans the recovery directory on every idle pass.