fix(fuse): allocate a listing's inodes before taking the state lock

A cold listing allocated the local id of every new child in a write
transaction while it held the global state lock, then marked the folder
listed, still under it. Every inline handler of every mount waited on that
transaction, and the transaction on the write connection the drain shares.
The ids are now allocated before the lock is taken, and the folder is marked
listed through the outbox after its children's rows, so a crash in between
no longer leaves it recorded as complete and empty (B221).
This commit is contained in:
nvrl committed 2026-10-16 10:05:58 +02:00
1 parent 77dac97afe
commit 830cfd9faa
4 files changed
+127 -15

No files matched your search

+30
View File
@@ -100,6 +100,7 @@ a live account since.
| Entry | Title |
|---|---|
| [B221](#b221--a-cold-listing-holds-the-global-lock-across-a-database-transaction) | A cold listing holds the global lock across a database transaction |
| [B220](#b220--thumbnails-on-a-stalled-link-hold-up-every-other-request) | Thumbnails on a stalled link hold up every other request |
| [B219](#b219--listing-a-shared-folder-freezes-the-whole-mount-on-a-slow-link) | Listing a shared folder freezes the whole mount on a slow link |
| [B218](#b218--pdfs-diagnose-freezes-the-mount-while-it-checks-the-database) | `pdfs diagnose` freezes the mount while it checks the database |
@@ -323,6 +324,35 @@ a live account since.
Newest first.
### B221 — A cold listing holds the global lock across a database transaction
**Status:** Fixed, not verified live
**Found:** 2026-10-16, architecture audit (`FINDINGS.md` 2.1.4).
**Where:** `Core::list_children` in `crates/pdfs-fuse/src/lib.rs`; `State::intern_batch_with_inos`,
`inos_for` and `State::record_listed` in `crates/pdfs-fuse/src/state.rs`; `serve_mkdir` in
`crates/pdfs-fuse/src/filesystem.rs`
**Repro:** List a large folder for the first time while another program stats files elsewhere in
the mount, and while the drain is busy.
**Cause.** A new node's inode comes from its row's local id, which `Db::lids_for` allocates in a
write transaction. A cold listing ran that transaction for every new child while it held the
global state lock, and then marked the folder listed, also under the lock. Every inline handler
of every mount waits on that lock, and the transaction itself waits on the single write
connection that the drain and the sync engine also use. That went against the `StateGuard`
outbox, which exists so that the lock is released before the writes are done. Marking the folder
listed before its children's rows were written also meant a crash in between left a folder
recorded as complete with no children.
**Fix.** `inos_for` allocates the listing's ids before the lock is taken, and
`intern_batch_with_inos` uses them. Marking a folder listed goes through the outbox with
`record_listed`, after the children's rows.
**Test:** `state::tests::inodes_allocated_before_the_lock_are_the_ones_the_node_keeps`,
`state::tests::a_listing_is_recorded_complete_after_its_children`.
---
### B220 — Thumbnails on a stalled link hold up every other request
**Status:** Fixed, not verified live