mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 00:47:00 +01:00
fix(fuse): a file read just as its create lands has its bytes (B179)
This commit is contained in:
1 parent
a6f019ebe6
commit
4e0fd3c0b1
6 files changed
+81
-6
No files matched your search
@@ -3225,8 +3225,19 @@ impl Core {
|
|||||||
/// `None` while it is still queued. Read from the row, which has it before
|
/// `None` while it is still queued. Read from the row, which has it before
|
||||||
/// the tree does.
|
/// the tree does.
|
||||||
pub(crate) fn landed_row_uid(&self, ino: u64) -> Option<NodeUid> {
|
pub(crate) fn landed_row_uid(&self, ino: u64) -> Option<NodeUid> {
|
||||||
state::ino_lid(ino)
|
state::ino_lid(ino).and_then(|lid| self.landed_lid_uid(lid))
|
||||||
.and_then(|lid| self.db.uid_of_lid(lid).ok().flatten())
|
}
|
||||||
|
|
||||||
|
/// [`Core::landed_row_uid`] for a node known by its stand-in `uid`.
|
||||||
|
pub(crate) fn landed_stand_in_uid(&self, uid: &NodeUid) -> Option<NodeUid> {
|
||||||
|
pdfs_core::db::local_lid(&uid.to_string()).and_then(|lid| self.landed_lid_uid(lid))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn landed_lid_uid(&self, lid: i64) -> Option<NodeUid> {
|
||||||
|
self.db
|
||||||
|
.uid_of_lid(lid)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
.and_then(|uid| parse_node_uid(&uid))
|
.and_then(|uid| parse_node_uid(&uid))
|
||||||
.filter(|uid| !is_local_uid(uid))
|
.filter(|uid| !is_local_uid(uid))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -499,9 +499,14 @@ impl Core {
|
|||||||
}
|
}
|
||||||
// A node created offline and never written has no blob and no remote: it
|
// A node created offline and never written has no blob and no remote: it
|
||||||
// is an empty file, and asking the API about a `local~` uid would only
|
// is an empty file, and asking the API about a `local~` uid would only
|
||||||
// earn a 404 (offline.md Phase 3b).
|
// earn a 404 (offline.md Phase 3b). Unless its create landed since the
|
||||||
|
// read looked: the landing moved the queued write to the uid Drive
|
||||||
|
// gave it, and the row says which (`docs/BUGS.md` B179).
|
||||||
if is_local_uid(uid) {
|
if is_local_uid(uid) {
|
||||||
return Ok(Vec::new());
|
return match self.landed_stand_in_uid(uid) {
|
||||||
|
Some(real) => self.read_range(&real, mtime, fsize, offset, len, cache_blocks),
|
||||||
|
None => Ok(Vec::new()),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
self.read_range_remote(uid, mtime, fsize, offset, len, cache_blocks)
|
self.read_range_remote(uid, mtime, fsize, offset, len, cache_blocks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -609,6 +609,41 @@ 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_file_read_as_its_create_lands_has_its_bytes() {
|
||||||
|
// The landing moved the queued write to the uid Drive gave the file.
|
||||||
|
// A read that still knew the file by its stand-in found no write
|
||||||
|
// queued under it, took the file for one never written and read it
|
||||||
|
// empty (B179).
|
||||||
|
let drive = FakeDrive::new();
|
||||||
|
let dir = scratch("read-as-create-lands");
|
||||||
|
let daemon = Daemon::start(&dir, drive.client(1, Faults::lan())).unwrap();
|
||||||
|
let mnt = daemon.mountpoint.clone();
|
||||||
|
let create = daemon.client.hold_reply_to_next_create();
|
||||||
|
std::fs::write(mnt.join("a.txt"), b"old").unwrap();
|
||||||
|
assert!(wait_until(Duration::from_secs(30), || create.reached()));
|
||||||
|
std::fs::write(mnt.join("a.txt"), b"newer").unwrap();
|
||||||
|
let uid = drive.lookup("a.txt").unwrap();
|
||||||
|
let landing = daemon.client.hold_next_landing(&uid);
|
||||||
|
drop(create);
|
||||||
|
assert!(wait_until(Duration::from_secs(30), || landing.reached()));
|
||||||
|
assert_eq!(std::fs::read(mnt.join("a.txt")).unwrap(), b"newer");
|
||||||
|
drop(landing);
|
||||||
|
assert!(
|
||||||
|
wait_until(Duration::from_secs(30), || {
|
||||||
|
daemon.pending().is_ok_and(|items| items.is_empty())
|
||||||
|
&& drive.tree().get("a.txt") == Some(&Entry::File(Arc::new(b"newer".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,6 +12,29 @@ Conventions:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## B179 — A file read just as its create lands reads empty
|
||||||
|
|
||||||
|
**Status:** Fixed (unverified).
|
||||||
|
**Found:** 2026-10-04, by the account run on 378667f, on on-demand sync folders: the unusual
|
||||||
|
names case read `<angle>|pipe` back as 0 bytes instead of 12, and B70's completed download as 0
|
||||||
|
bytes instead of 147456. Both files had a write that landed during their create, which the
|
||||||
|
daemon queued as a revision.
|
||||||
|
|
||||||
|
**Where:** `Core::read_range` in `crates/pdfs-fuse/src/reads.rs`.
|
||||||
|
|
||||||
|
**Cause.** A read takes the file's uid from the tree and reads on a transfer worker. A file made
|
||||||
|
on the mount has its stand-in uid there until its create lands. When it lands, the queued write
|
||||||
|
moves to the uid Drive gave the file. A read that still asked by the stand-in found no write
|
||||||
|
queued under it, took the file for one created and never written, and returned nothing. The
|
||||||
|
read waits on the transfer lane, which a run keeps busy, so the window is not small.
|
||||||
|
|
||||||
|
**Fix.** A stand-in with no queued write is looked up by its local id. Once its create has
|
||||||
|
landed, the read goes on under the uid Drive gave it.
|
||||||
|
|
||||||
|
**Test:** `a_file_read_as_its_create_lands_has_its_bytes` in `crates/pdfs-fuse/src/sim/daemon.rs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## B178 — Trashing a folder while sync is paused keeps a create in it as a trash
|
## B178 — Trashing a folder while sync is paused keeps a create in it as a trash
|
||||||
|
|
||||||
**Status:** Fixed (unverified).
|
**Status:** Fixed (unverified).
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ by its local id, in the queue, the stored tree and pins alike.
|
|||||||
(B177).
|
(B177).
|
||||||
- **Deleting a folder while sync is paused no longer leaves a queued change behind** for a
|
- **Deleting a folder while sync is paused no longer leaves a queued change behind** for a
|
||||||
file in it that was never uploaded (B178).
|
file in it that was never uploaded (B178).
|
||||||
|
- **A file read just as it reaches Drive no longer reads empty** (B179).
|
||||||
- **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).
|
||||||
|
|||||||
@@ -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 B178.
|
- Fixes from the simulation and account runs since then: B127 and B141 to B179.
|
||||||
- **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`.
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
The account run on 378667f: a folder listed as its create landed asked Drive by
|
The account run on 378667f: a folder listed as its create landed asked Drive by
|
||||||
its stand-in and answered `EIO` (B177, fixed). A folder trashed while sync was
|
its stand-in and answered `EIO` (B177, fixed). A folder trashed while sync was
|
||||||
paused kept a create below it as a trash: a paused drain worker had claimed it
|
paused kept a create below it as a trash: a paused drain worker had claimed it
|
||||||
(B178, fixed).
|
(B178, fixed). A file read as its create landed read empty (B179, fixed).
|
||||||
5. **Release**: 2.10.0 is tagged; the 2.9.0 tag still waits for the user's go-ahead.
|
5. **Release**: 2.10.0 is tagged; the 2.9.0 tag still waits for the user's go-ahead.
|
||||||
|
|
||||||
## After 3.0.0
|
## After 3.0.0
|
||||||
|
|||||||
Reference in new issue
Block a user