fix(fuse): stop reads deadlocking on size upgrades and retry stale renames

A read that settles a provisional size invalidated the file's page cache
while the kernel held that read's page lock, so the worker waited on
itself forever (B110). Size upgrades now invalidate attributes only.

Renaming a node right after renaming it could be refused by Drive as out
of date, because the name hash read just before still held the old name.
Every remote rename now goes through Core::rename_remote, which retries
InvalidRequirements for up to ten seconds (B111).
This commit is contained in:
nvrl committed 2026-09-29 19:47:20 +02:00
1 parent f252b09757
commit 54e9921efb
5 files changed
+181 -11

No files matched your search

+43
View File
@@ -12,6 +12,49 @@ Conventions:
---
## B111 — Renaming a file back right after renaming it fails with EIO
**Status:** Fixed (unverified).
**Found:** 2026-09-29, by the acceptance case "unusual but legal names", on My files and on the
on-demand folder. The case renames each file to `renamed-N` and back at once. One rename back per
run failed with `EIO`, and the journal showed `rename failed ... proton api error
InvalidRequirements (http 422): This file or folder was out of date, rename failed.`
**Where:** every remote rename: `serve_rename` in `crates/pdfs-fuse/src/filesystem.rs`,
`drain_rename_in_place` in `crates/pdfs-fuse/src/drain.rs`, and the control `rename` in
`crates/pdfs-fuse/src/lib.rs`.
**Cause.** The SDK's `rename_node` reads the node's details from the server and sends its current
name hash as `OriginalHash`. Right after a rename of the same node, that read can still return the
old hash. Drive then refuses the request as out of date. The daemon reported this at once as
`EIO`, although the same request succeeds a moment later.
**Fix.** All remote renames go through `Core::rename_remote`. It retries a rename that fails with
`InvalidRequirements` up to 20 times, half a second apart, the same window the move after a
rename already uses. Any other error still returns at once.
## B110 — A read that settles a provisional size deadlocks on its own page lock
**Status:** Fixed (unverified).
**Found:** 2026-09-29, by the acceptance case "application workloads", which timed out on My
files and on the on-demand folder on a fast network. The journal showed `a fuse worker has held
the same job for a long time worker=pdfs-fuse-8 job=read` growing past 700 s with no network
error. `/proc/<pid>/task/*/wchan` put that worker, and one more, in `__folio_lock`, while the
daemon had no file on any mount open.
**Where:** `crates/pdfs-fuse/src/lib.rs` (`Core::apply_size_upgrade`).
**Cause.** B100 made `read` settle a provisional size before it fetches, through
`resolve_provisional_size` and `apply_size_upgrade`. That function tells the kernel about the new
size with `inval_inode(ino, 0, 0)`, which also drops the file's page cache. The kernel holds the
page lock of the read being served until the daemon replies. The invalidation waits for the same
lock, on the thread that has to send the reply, so neither ever finishes. The process that
issued the read hangs, and so does anything else that touches those pages.
**Fix.** The size upgrade invalidates attributes only (`offset -1`). A size upgrade does not
change content, so the page cache has nothing stale to drop. A daemon already stuck this way
needs a restart.
## B109 — A re-listing shrinks a file that is open for writing, and SQLite reads it as malformed
**Status:** Fixed (unverified).
+6
View File
@@ -40,6 +40,12 @@ scratch (user data in `staging/` and `recovery/` is never touched by this).
- **Editors can save over a file several times in a row.** The fix for B104 could leave the
upload of a replaced file queued, and the next save onto the same name failed with
`Input/output error` (B106).
- **Renaming a file back right after renaming it no longer fails.** Drive can briefly call the
second rename out of date, and the daemon now waits it out instead of returning an I/O error
(B111).
- **Reading a freshly listed file no longer hangs.** When a read had to fetch the file's real
size first, the daemon could wait on the read it was serving, and the program froze until
the daemon restarted (B110).
- **Databases and other files kept open while written no longer read back empty.** When the
folder was listed again while a file was still open, its size dropped to what Drive held,
often 0 bytes, and SQLite reported `database disk image is malformed` (B109).