fix(fuse): list a new file once while its create lands (B164)

A listing from Drive overlays what is still queued for the folder. A create
Drive has made but whose answer is on its way, or which the drain is still
reading back, was in both: Drive's node under its new uid and the queue's
stand-in, under one name. A node queued in the folder now holds its name there.
This commit is contained in:
nvrl committed 2026-10-04 19:03:33 +02:00
1 parent b37d5e7928
commit ce2bc0ac84
5 files changed
+80 -4

No files matched your search

+7 -1
View File
@@ -6496,11 +6496,17 @@ fn node_visible(node: &Node, folder_uid: &NodeUid, hidden: &HashSet<NodeUid>) ->
/// Lay what is queued for a folder over Drive's listing of it, from
/// `Db::queued_children`: a node a queued rename moved away is dropped, and a
/// node a queued create, mkdir or rename put there is listed as the DB has it.
///
/// That node holds its name here: a create Drive already has but whose answer
/// is still on its way, or which the drain is still reading back, is listed by
/// Drive under its new uid and by the queue under its stand-in. Both were
/// listed, and a 128-file folder listed 129 (`docs/BUGS.md` B164).
fn overlay_queued(nodes: &mut Vec<Node>, queued: Vec<Node>, gone: &HashSet<String>) {
let here: HashSet<String> = queued.iter().map(|node| node.uid.to_string()).collect();
let names: HashSet<&str> = queued.iter().map(|node| node.name.as_str()).collect();
nodes.retain(|node| {
let uid = node.uid.to_string();
!gone.contains(&uid) && !here.contains(&uid)
!gone.contains(&uid) && !here.contains(&uid) && !names.contains(node.name.as_str())
});
nodes.extend(queued);
}
+41
View File
@@ -554,6 +554,47 @@ mod tests {
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
fn a_create_drive_lists_before_it_is_read_back_is_listed_once() {
// The feed reported a new file while the drain still read it back, so
// the folder was listed from Drive again, and the file came twice: as
// Drive's node and as the queued create's stand-in. A 128-file folder
// listed 129 (B164).
let drive = FakeDrive::new();
let dir = scratch("listed-before-retired");
let daemon = Daemon::start(&dir, drive.client(1, Faults::lan())).unwrap();
let mnt = daemon.mountpoint.clone();
std::fs::create_dir(mnt.join("x")).unwrap();
assert!(wait_until(Duration::from_secs(30), || {
daemon.pending().is_ok_and(|items| items.is_empty()) && drive.lookup("x").is_some()
}));
let create = daemon.client.hold_reply_to_next_create();
std::fs::write(mnt.join("x/c.bin"), b"abc").unwrap();
assert!(wait_until(Duration::from_secs(30), || create.reached()));
let uid = drive.lookup("x/c.bin").unwrap();
let read_back = daemon.client.hold_next_read(&uid);
drop(create);
assert!(wait_until(Duration::from_secs(30), || read_back.reached()));
// Long enough for the next event poll to bring the file's event.
std::thread::sleep(crate::POLL_INTERVAL + Duration::from_secs(3));
let names: Vec<_> = std::fs::read_dir(mnt.join("x"))
.unwrap()
.map(|e| e.unwrap().file_name())
.collect();
drop(read_back);
assert_eq!(names, ["c.bin"]);
assert!(wait_until(Duration::from_secs(30), || {
daemon.pending().is_ok_and(|items| items.is_empty())
}));
assert_eq!(std::fs::read(mnt.join("x/c.bin")).unwrap(), b"abc");
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_folder_made_again_while_the_old_one_is_trashed_is_a_new_folder() {