mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 00:47:00 +01:00
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:
1 parent
b37d5e7928
commit
ce2bc0ac84
5 files changed
+80
-4
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -12,6 +12,33 @@ Conventions:
|
||||
|
||||
---
|
||||
|
||||
## B164 — A folder lists a new file twice while its create lands
|
||||
|
||||
**Status:** Fixed.
|
||||
**Found:** 2026-10-04, by the account run on 2.10.0 ("a wide directory", on-demand pairing 2).
|
||||
Eight threads wrote 128 files into a new folder, and the listing that followed held 129
|
||||
entries. Each name was created on Drive once, and nothing was logged.
|
||||
|
||||
**Where:** `overlay_queued` in `crates/pdfs-fuse/src/lib.rs`.
|
||||
|
||||
**Cause.** A listing from Drive shows what is still queued for the folder over what Drive has
|
||||
(B132). A create Drive has made but whose answer is still on its way, or which the drain is
|
||||
still reading back, is in both: Drive lists it under its new uid, the queue under its stand-in.
|
||||
That listing only happens when the folder's listing was dropped, and the feed's event for a
|
||||
new file does that when it comes before the drain has noted the create as its own. Both were
|
||||
listed under the same name.
|
||||
|
||||
The run logged nothing for the folder, so the event is inferred, not observed. It is the one
|
||||
path found that lists a name twice.
|
||||
|
||||
**Fix.** A node queued in the folder holds its name there: Drive's node of the same name is
|
||||
left out of the listing until the queued op lands.
|
||||
|
||||
**Test:** simulation test `a_create_drive_lists_before_it_is_read_back_is_listed_once` (failed
|
||||
before the fix: the folder listed `c.bin` twice).
|
||||
|
||||
---
|
||||
|
||||
## B163 — A file read right after its create landed is empty
|
||||
|
||||
**Status:** Fixed.
|
||||
|
||||
@@ -55,6 +55,8 @@ by its local id, in the queue, the stored tree and pins alike.
|
||||
inside the removed folder and was trashed along with it (B162).
|
||||
- **A new file no longer reads as empty just after it is uploaded.** Drive can list it a moment
|
||||
before its contents are committed, and the mount took that answer (B163).
|
||||
- **A folder no longer lists a new file twice while it is uploaded.** A listing taken as the
|
||||
upload landed showed it both as Drive had it and as the queue did (B164).
|
||||
- **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
|
||||
|
||||
@@ -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 B163.
|
||||
- Fixes from the simulation and account runs since then: B127 and B141 to B164.
|
||||
- **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`.
|
||||
|
||||
@@ -99,8 +99,8 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
||||
mirror move carries the local copy along. The journal showed a conflict copy retrying
|
||||
into a deleted folder (B150, fixed). Not confirmed yet. The run on 2.10.0
|
||||
(2026-10-04: 260 passed, 3 failed) failed a new file that read back empty (B163,
|
||||
fixed), a 128-entry folder that listed 129, and its cleanup, which ran while the
|
||||
daemon was restarting.
|
||||
fixed), a 128-entry folder that listed 129 (B164, fixed), and its cleanup, which ran
|
||||
while the daemon was restarting.
|
||||
5. **Release**: 2.10.0 is tagged; the 2.9.0 tag still waits for the user's go-ahead.
|
||||
|
||||
## After 3.0.0
|
||||
|
||||
Reference in new issue
Block a user