Skip to content

v1.7.0

Choose a tag to compare

@github-actions github-actions released this 15 Aug 22:47
· 488 commits to main since this release

[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 every SELECT-only method
    routed at it, so a read no longer waits on the write connection.
  • nodes.path persisted and backfilled, replacing the recursive path_of walk.
  • Single-writer flock on the database; a SQLITE_CORRUPT/NOTADB file is quarantined as
    cache.db.corrupt-<ts> (WAL sidecars too) and rebuilt rather than failing the mount.
  • VACUUM and the deep integrity_check now ack immediately and run as background jobs;
    pdfs doctor polls 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 — Status gained parked_uploads, failing_ops,
    failing_error, staged_bytes, staged_oldest_secs; pdfs status prints them when
    non-zero.
  • statfs implemented against the account quota; FUSE init negotiates max_readahead,
    max_background, PARALLEL_DIROPS and CACHE_SYMLINKS; blksize raised to 1 MiB.

Changed

  • State no 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 cap stat answers 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: ENOSPC is now propagated from write/fsync/staging instead of being
    swallowed, with emergency eviction wired up and scratch marked durable before any staging
    error; a failed record_pending_write fails release rather than dropping the write.
  • Data loss: the conflict sweep no longer discards queued operations after the trash
    round trip.
  • Data loss: rescue_scratch moves a blob with an unreadable sidecar to recovery/
    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
    limit clamped.
  • recover_fsynced_writes no longer scans the recovery directory on every idle pass.