From 31863a1da1ae1c6f26b32748ce2ed71109aa1d27 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Mon, 5 Oct 2026 04:50:10 +0200 Subject: [PATCH] fix(fuse): a new file closed empty reaches Drive at once (B192) --- crates/pdfs-fuse/src/lib.rs | 8 +++++++- crates/pdfs-fuse/src/sim/daemon.rs | 28 ++++++++++++++++++++++++++++ docs/BUGS.md | 17 +++++++++++++++++ docs/CHANGELOG.md | 3 ++- docs/MILESTONE-3.0.0-STATUS.md | 5 +++-- 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/crates/pdfs-fuse/src/lib.rs b/crates/pdfs-fuse/src/lib.rs index 14406bc..1adab87 100644 --- a/crates/pdfs-fuse/src/lib.rs +++ b/crates/pdfs-fuse/src/lib.rs @@ -3334,6 +3334,12 @@ impl Core { if !h.dirty { self.cache.clear_scratch_durable(&h.path); let _ = std::fs::remove_file(&h.path); + // A new file's create waits for this release to wake the drain + // (`Core::queue_local_node`). Closed empty, it attached nothing + // that would, and sat until the idle poll (`docs/BUGS.md` B192). + if is_local_uid(&h.uid) { + self.wake_drain(); + } return Ok(()); } preserve_on_access_denied(self.require_uid_writable(&h.uid), h.dirty, || { @@ -3828,7 +3834,7 @@ impl Core { debug!(%uid, %parent_uid, name, is_dir, "created node offline; queued"); // A folder is due now; without this the workers slept through it to // the idle poll (B160). A file is not: its bytes ride on the create, and - // the release that attaches them wakes the drain. + // its release wakes the drain, with bytes or without (B192). if is_dir { self.wake_drain(); } diff --git a/crates/pdfs-fuse/src/sim/daemon.rs b/crates/pdfs-fuse/src/sim/daemon.rs index 2be180b..e02476e 100644 --- a/crates/pdfs-fuse/src/sim/daemon.rs +++ b/crates/pdfs-fuse/src/sim/daemon.rs @@ -383,6 +383,34 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + #[test] + #[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"] + fn a_file_made_empty_while_the_drain_is_idle_reaches_drive_at_once() { + // A new file's create waits for the release that attaches its bytes + // to wake the drain. A file closed without any attached none, so it + // sat until the workers' 30 s idle poll (B192). + let drive = FakeDrive::new(); + let dir = scratch("idle-empty-create"); + let daemon = Daemon::start(&dir, drive.client(1, Faults::lan())).unwrap(); + assert!(wait_until(Duration::from_secs(30), || { + daemon.pending().is_ok_and(|items| items.is_empty()) + })); + + std::fs::write(daemon.mountpoint.join("e.md"), b"").unwrap(); + assert!( + wait_until(Duration::from_secs(10), || { + daemon.pending().is_ok_and(|items| items.is_empty()) + && drive.lookup("e.md").is_some() + }), + "{:?} {:?}", + daemon.pending(), + drive.tree() + ); + + assert!(matches!(daemon.stop(), Ok(MountOutcome::Shutdown))); + let _ = std::fs::remove_dir_all(&dir); + } + #[test] #[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"] fn a_file_whose_upload_was_not_read_back_takes_two_writes_at_once() { diff --git a/docs/BUGS.md b/docs/BUGS.md index a677835..d4ef5ae 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -12,6 +12,23 @@ Conventions: --- +## B192 — A new file closed empty reaches Drive only half a minute later + +**Status:** Fixed. +**Found:** 2026-10-05, while writing the test for B193. An empty file made on an idle mount was +still queued 30 s later. + +**Where:** `Core::queue_revision` in `crates/pdfs-fuse/src/lib.rs`. + +**Cause.** Queueing a file's create does not wake the drain: its bytes ride on the create, and +the release that attaches them wakes it (B160). A file closed without a write (`touch`, `: > f`) +attaches nothing, so its create waited for the workers' 30 s idle poll. + +**Fix.** Closing a clean handle of a file whose create is queued wakes the drain. + +**Test:** simulation test `a_file_made_empty_while_the_drain_is_idle_reaches_drive_at_once` +(failed before the fix: the file was still queued after 10 s). + ## B191 — A file deleted after its create failed and was written again can stay on Drive **Status:** Fixed (unverified). diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index dbac999..993b875 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -54,7 +54,8 @@ by its local id, in the queue, the stored tree and pins alike. (B158). - **Opening a file right after the daemon restarts no longer fails with "Resource temporarily unavailable"** (B159). -- **A new folder reaches Drive at once**, not up to half a minute later (B160). +- **A new folder, or a new empty file, reaches Drive at once**, not up to half a minute later + (B160, B192). - **A file moved while it was still being uploaded keeps its new name in the mount** on a slow link (B161). - **A folder removed and made again at once keeps what goes into it.** It used to be created diff --git a/docs/MILESTONE-3.0.0-STATUS.md b/docs/MILESTONE-3.0.0-STATUS.md index fa65b79..4e08052 100644 --- a/docs/MILESTONE-3.0.0-STATUS.md +++ b/docs/MILESTONE-3.0.0-STATUS.md @@ -9,7 +9,7 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase - **Phase 2**: local ids, inode = local id, one tree for every mount (2.10.0). - **Phase 3, first part**: mkdir, create, rename, unlink and rmdir on the mount are recorded locally and sent from the queue; `"local_first": false` brings back the old path (5e0f30c). -- Fixes from the simulation and account runs since then: B125, B127, B130 and B141 to B191. +- Fixes from the simulation and account runs since then: B125, B127, B130 and B141 to B192. - **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app, tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`; Discard button and `pdfs sync discard`; a warning badge on the node in the file browser. @@ -118,7 +118,8 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase after an outage only the drain had met (B189, fixed; older than 3.0.0), and a file saved just before a stop whose release the stop dropped (B190, fixed; older too). The run on 1436c5f found a file deleted after its failed create was written again left on Drive - (B191, fixed). + (B191, fixed). Its test for B193 found a new empty file waiting out the idle poll (B192, + fixed). - [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed) failed B113 and B121 on every on-demand mount, two mirror-to-mirror move cases, and the journal check. B121 was a bug (B149, fixed); B113's case still expected creates