fix(fuse): a folder whose mkdir is queued is listed from the database (B125)

This commit is contained in:
nvrl committed 2026-10-05 00:02:40 +02:00
1 parent f9b1638603
commit e743cd60cf
5 files changed
+72 -6

No files matched your search

+11
View File
@@ -2521,6 +2521,17 @@ impl Core {
Ok(None) => {} Ok(None) => {}
Err(e) => warn!(%folder_uid, error = %e, "db children_if_listed failed"), Err(e) => warn!(%folder_uid, error = %e, "db children_if_listed failed"),
} }
// Drive knows nothing by a stand-in. Until its mkdir lands the folder
// is only here, and once it has, the DB holds what Drive does. A
// listing dropped meanwhile was asked of Drive, and every lookup in
// the folder failed with EIO (`docs/BUGS.md` B125).
if is_local_uid(&folder_uid) {
let nodes = self.db.known_children(&folder_uid).map_err(|e| {
warn!(%folder_uid, error = %e, "db known_children failed");
Errno::EIO
})?;
return self.adopt_db_listing(ino, &folder_uid, nodes, primary_root);
}
if !self.is_online() { if !self.is_online() {
return self.adopt_stale_listing(ino, &folder_uid, cached, primary_root); return self.adopt_stale_listing(ino, &folder_uid, cached, primary_root);
} }
+44
View File
@@ -278,6 +278,7 @@ pub(crate) fn wait_until(limit: Duration, mut done: impl FnMut() -> bool) -> boo
mod tests { mod tests {
use super::*; use super::*;
use crate::sim::fake_drive::{Entry, FakeDrive, Faults}; use crate::sim::fake_drive::{Entry, FakeDrive, Faults};
use pdfs_core::control::RefreshScope;
use std::collections::BTreeMap; use std::collections::BTreeMap;
#[test] #[test]
@@ -644,6 +645,49 @@ mod tests {
let _ = std::fs::remove_dir_all(&dir); let _ = std::fs::remove_dir_all(&dir);
} }
#[test]
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
fn a_folder_whose_mkdir_is_queued_lists_after_a_refresh() {
// Once its listing was dropped, the folder was asked of Drive by its
// stand-in, and every listing and lookup in it failed with EIO until
// its mkdir landed (B125).
let drive = FakeDrive::new();
let dir = scratch("queued-folder-refresh");
let daemon = Daemon::start(&dir, drive.client(1, Faults::lan())).unwrap();
let mnt = daemon.mountpoint.clone();
let paused = |paused| Request::SetSyncPaused {
paused,
until: None,
};
daemon.request(&paused(true)).unwrap();
std::fs::create_dir(mnt.join("d")).unwrap();
std::fs::write(mnt.join("d/f.txt"), b"queued").unwrap();
let path = mnt.join("d").to_string_lossy().into_owned();
let refresh = Request::Refresh {
scope: RefreshScope::Dir { path: path.clone() },
};
assert!(matches!(daemon.request(&refresh), Ok(Response::Ok { .. })));
let Ok(Response::Entries { entries }) = daemon.request(&Request::ListDir { path }) else {
panic!("the daemon did not list d");
};
let names: Vec<_> = entries.iter().map(|entry| entry.name.as_str()).collect();
assert_eq!(names, ["f.txt"]);
assert_eq!(std::fs::read(mnt.join("d/f.txt")).unwrap(), b"queued");
daemon.request(&paused(false)).unwrap();
assert!(
wait_until(Duration::from_secs(30), || {
daemon.pending().is_ok_and(|items| items.is_empty())
&& drive.tree().get("d/f.txt")
== Some(&Entry::File(Arc::new(b"queued".to_vec())))
}),
"{:?} {:?}",
daemon.pending(),
drive.tree()
);
assert!(matches!(daemon.stop(), Ok(MountOutcome::Shutdown)));
let _ = std::fs::remove_dir_all(&dir);
}
#[test] #[test]
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"] #[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
fn a_file_moved_while_its_create_was_on_the_wire_keeps_its_new_name() { fn a_file_moved_while_its_create_was_on_the_wire_keeps_its_new_name() {
+12 -3
View File
@@ -1344,7 +1344,7 @@ profile fails on it when it comes up, which depends on timing.
## B125 — A folder made offline fails with `EIO` once its listing is invalidated ## B125 — A folder made offline fails with `EIO` once its listing is invalidated
**Status:** Open. **Status:** Fixed.
**Found:** 2026-10-03, by the simulation runs (`sim::run`, profile one-client-flaky, seeds 3 **Found:** 2026-10-03, by the simulation runs (`sim::run`, profile one-client-flaky, seeds 3
and 4). Right after a restart, `rmdir y` (seed 3), and a write to a file in the client's folder and 4). Right after a restart, `rmdir y` (seed 3), and a write to a file in the client's folder
(seed 4), failed with `EIO`. The daemon logged "enumerate folder children failed" with Drive's (seed 4), failed with `EIO`. The daemon logged "enumerate folder children failed" with Drive's
@@ -1362,8 +1362,17 @@ uid. Drive answers 404, and every lookup in the folder fails with `EIO` until it
Right after it lands, Drive may not list the new folder yet (the lag of B118). Then the listing Right after it lands, Drive may not list the new folder yet (the lag of B118). Then the listing
fails with a 404 for the real uid too. fails with a 404 for the real uid too.
**Test:** the simulation runs count it and go on. With `PDFS_SIM_KNOWN=fail` the flaky profile **Fix.** A folder still known by its stand-in is listed from the database and never asked of
fails on it. Drive. Until its mkdir lands the folder is only here; once it has, the database holds what Drive
does. The second case, a listing by the real uid that Drive does not show yet, needs a create
that landed without being read back: the read-back waits up to 1.25 s for Drive to list the
folder, longer than the lag of any simulation profile. The runs keep counting both until they
show neither again.
**Test:** `a_folder_whose_mkdir_is_queued_lists_after_a_refresh` in
`crates/pdfs-fuse/src/sim/daemon.rs`: with sync paused, a folder is made with a file in it, a
refresh drops its listing, and it is listed again. With `PDFS_SIM_KNOWN=fail` the flaky profile
fails on it in the simulation runs; seeds 3 and 4 no longer reach it since phase 3.
## B124 — After a replacing rename fails, the file it was replacing is gone from the mount ## B124 — After a replacing rename fails, the file it was replacing is gone from the mount
+2
View File
@@ -86,6 +86,8 @@ by its local id, in the queue, the stored tree and pins alike.
- **A file read just as it reaches Drive no longer reads empty** (B179). - **A file read just as it reaches Drive no longer reads empty** (B179).
- **Stopping the daemon no longer takes 10 seconds more than it needs** when an idle upload - **Stopping the daemon no longer takes 10 seconds more than it needs** when an idle upload
worker missed the stop (B180). worker missed the stop (B180).
- **A folder made while offline or with sync paused no longer fails with "Input/output
error"** once it is refreshed or the connection comes back (B125).
- **A file renamed just as its upload finished keeps its new name.** The mount no longer showed - **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, the old name, and a second rename no longer failed with "No such file or directory" (B152,
B172). B172).
+3 -3
View File
@@ -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 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 - **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). 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 B180. - Fixes from the simulation and account runs since then: B125, B127 and B141 to B180.
- **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app, - **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`. tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`.
@@ -89,8 +89,8 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
create was on the wire, which the mount showed under its old name (B161, fixed). create was on the wire, which the mount showed under its old name (B161, fixed).
Seed 4 failed on a folder removed and made again, whose create adopted the removed Seed 4 failed on a folder removed and made again, whose create adopted the removed
folder (B162, fixed). CI on 903452a passed every profile. folder (B162, fixed). CI on 903452a passed every profile.
- [ ] Known-bug list in `sim/run.rs` is empty: B125 and B130 open; B127, B129 and B132 fixed - [ ] Known-bug list in `sim/run.rs` is empty: B130 open; B125, B127, B129 and B132 fixed but
but unconfirmed. unconfirmed.
- [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed) - [ ] 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 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 the journal check. B121 was a bug (B149, fixed); B113's case still expected creates