diff --git a/crates/pdfs-fuse/src/lib.rs b/crates/pdfs-fuse/src/lib.rs index 2459978..34fd14f 100644 --- a/crates/pdfs-fuse/src/lib.rs +++ b/crates/pdfs-fuse/src/lib.rs @@ -3688,6 +3688,12 @@ impl Core { Errno::EIO })?; 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. + if is_dir { + self.wake_drain(); + } Ok(node) } diff --git a/crates/pdfs-fuse/src/sim/daemon.rs b/crates/pdfs-fuse/src/sim/daemon.rs index a57b93a..bec3447 100644 --- a/crates/pdfs-fuse/src/sim/daemon.rs +++ b/crates/pdfs-fuse/src/sim/daemon.rs @@ -358,6 +358,30 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + #[test] + #[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"] + fn a_folder_made_while_the_drain_is_idle_reaches_drive_at_once() { + // A new folder queued its create without waking the drain, so it sat + // until the workers' 30 s idle poll (B160). + let drive = FakeDrive::new(); + let dir = scratch("idle-mkdir"); + 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::create_dir(daemon.mountpoint.join("x")).unwrap(); + assert!( + wait_until(Duration::from_secs(10), || drive.lookup("x").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 bab1cea..79eaf8d 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -12,6 +12,27 @@ Conventions: --- +## B160 — A new folder reaches Drive only half a minute later + +**Status:** Fixed. +**Found:** 2026-10-04, by the simulation's slow-link profile (`one_client_on_a_slow_link`, +seed 4). Folders made last before the client waited for its queue reached Drive 30 s late. The +log showed each `mkdir` queued, then claimed exactly 30 s later. + +**Where:** `Core::queue_local_node` in `crates/pdfs-fuse/src/lib.rs`. + +**Cause.** The drain workers sleep until an op is due, or for up to 30 s when none is. Every +other way of queueing an op (a write, a rename, a trash) wakes them. Queueing a folder's create +did not, so with the workers asleep it waited for their next poll. A file's create did not need +the wake: its bytes ride on the create, and the release that attaches them wakes the drain. + +**Fix.** Queueing a folder's create wakes the drain. + +**Test:** simulation test `a_folder_made_while_the_drain_is_idle_reaches_drive_at_once` (failed +before the fix: the folder was still queued after 10 s). + +--- + ## B159 — A write right after a restart fails with "Resource temporarily unavailable" **Status:** Fixed (unverified). diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index db692ad..3531ace 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -48,6 +48,7 @@ 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 file renamed just as its upload finished keeps its new name.** The mount no longer showed the old name, and a second rename no longer failed with "No such file or directory" (B152). - **A change that waits for a name to be freed no longer waits seconds after it is.** A rename or diff --git a/docs/MILESTONE-3.0.0-STATUS.md b/docs/MILESTONE-3.0.0-STATUS.md index b96a7f3..29ad533 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: B127 and B141 to B159. +- Fixes from the simulation and account runs since then: B127 and B141 to B160. - **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`. @@ -84,7 +84,8 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase back (B157, fixed). Seed 3 then failed four of ten runs on two renames of one file landing out of order (B158, fixed). Flaky-link seed 4 failed one of ten runs on a lookup that answered `EAGAIN` while the root was listed again after a restart (B159, - fixed). + fixed). Slow-link seed 4 also had folders reach Drive 30 s late, after the + drain's idle poll (B160, fixed). - [ ] Known-bug list in `sim/run.rs` is empty: B125 and B130 open; B127, B129 and B132 fixed but unconfirmed. - [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed)