mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fix(fuse): a stop queues the writes whose release the kernel never sent (B190)
This commit is contained in:
1 parent
f0a43fb923
commit
1436c5f37e
6 files changed
+94
-2
No files matched your search
@@ -3281,6 +3281,42 @@ impl Core {
|
||||
.filter(|uid| !is_local_uid(uid))
|
||||
}
|
||||
|
||||
/// Queue what the write handles the kernel never released hold.
|
||||
///
|
||||
/// `close(2)` returns before the kernel sends `release`, and the stop
|
||||
/// aborts the FUSE connection with the releases still queued: a file a
|
||||
/// program had just saved was never queued, and Drive kept it as it was
|
||||
/// (`docs/BUGS.md` B190). Runs once every session has ended, so no release
|
||||
/// can race it. A handle still open in a program goes the same way, with
|
||||
/// what it wrote so far; one on an unlinked file only leaves its scratch.
|
||||
pub(crate) fn queue_unreleased_writes(&self) {
|
||||
let handles: Vec<(WriteHandle, bool)> = {
|
||||
let mut st = self.state();
|
||||
st.handles.clear();
|
||||
let inos: Vec<u64> = st.active_writes.keys().copied().collect();
|
||||
inos.into_iter()
|
||||
.filter_map(|ino| {
|
||||
let unlinked = st.entries.get(&ino).is_some_and(|e| e.unlinked);
|
||||
st.active_writes.remove(&ino).map(|h| (h, unlinked))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
for (mut h, unlinked) in handles {
|
||||
if unlinked {
|
||||
self.cache.clear_scratch_durable(&h.path);
|
||||
let _ = std::fs::remove_file(&h.path);
|
||||
continue;
|
||||
}
|
||||
self.follow_landed_create(&mut h);
|
||||
match self.queue_revision(&mut h) {
|
||||
Ok(()) => info!(uid = %h.uid, "queued a write the kernel never released"),
|
||||
Err(error) => {
|
||||
warn!(uid = %h.uid, ?error, "cannot queue a write the kernel never released")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept a released write handle's bytes and queue their upload
|
||||
/// (offline.md Phase 3).
|
||||
///
|
||||
|
||||
@@ -750,6 +750,7 @@ pub(crate) fn mount_with(
|
||||
warn!(id, error = %e, "unmount on-demand folder failed");
|
||||
}
|
||||
}
|
||||
core.queue_unreleased_writes();
|
||||
|
||||
// Last, because the workers may legitimately be finishing an upload, and
|
||||
// nothing they can still do is harmful once the mounts are down.
|
||||
|
||||
@@ -476,6 +476,37 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
|
||||
fn a_stop_keeps_what_a_handle_wrote_before_its_release() {
|
||||
// close(2) returns before the kernel sends the release, and the stop
|
||||
// aborted the connection with the release still queued: the bytes of a
|
||||
// file just saved were never queued, and Drive kept it empty (B190).
|
||||
// A handle still open at the stop is the same case, held open.
|
||||
use std::io::Write;
|
||||
let drive = FakeDrive::new();
|
||||
let dir = scratch("stop-before-release");
|
||||
let daemon = Daemon::start(&dir, drive.client(1, Faults::lan())).unwrap();
|
||||
let mut file = std::fs::File::create(daemon.mountpoint.join("f.txt")).unwrap();
|
||||
file.write_all(b"0123456789").unwrap();
|
||||
|
||||
let daemon = daemon.restart().unwrap();
|
||||
drop(file);
|
||||
assert!(
|
||||
wait_until(Duration::from_secs(30), || {
|
||||
daemon.pending().is_ok_and(|items| items.is_empty())
|
||||
&& drive.tree().get("f.txt")
|
||||
== Some(&Entry::File(Arc::new(b"0123456789".to_vec())))
|
||||
}),
|
||||
"{:?} {:?}",
|
||||
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_write_that_failed_on_a_short_outage_goes_out_soon_after_the_link_returns() {
|
||||
|
||||
Reference in new issue
Block a user