fix(fuse): a write closed just as its file's create lands is queued (B176)

This commit is contained in:
nvrl committed 2026-10-04 22:31:49 +02:00
1 parent ce60e006ac
commit 4f141c78ca
5 files changed
+56 -7

No files matched your search

+1 -1
View File
@@ -1806,7 +1806,7 @@ impl ProtonFs {
}
(Some(mut h), false) => {
self.core.follow_landed_create(&mut h);
match self.core.queue_revision(&h) {
match self.core.queue_revision(&mut h) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
+30 -4
View File
@@ -3242,7 +3242,7 @@ impl Core {
///
/// The scratch file is *moved* into staging, never copied: it is the only
/// copy of what the user wrote.
fn queue_revision(&self, h: &WriteHandle) -> Result<(), Errno> {
fn queue_revision(&self, h: &mut WriteHandle) -> Result<(), Errno> {
// Closing an untouched writable handle is not a mutation. It remains
// valid after a downgrade and only owes cleanup of its empty scratch.
if !h.dirty {
@@ -3266,9 +3266,15 @@ impl Core {
// recovery a second, stale copy of the same write.
self.cache.clear_scratch_durable(&h.path);
if is_local_uid(&h.uid) && !self.db.has_create_op(&h.uid.to_string()).unwrap_or(true) {
debug!(uid = %h.uid, "local node was unlinked before creation; dropping revision");
let _ = std::fs::remove_file(&h.path);
return Ok(());
// A create that landed since the handle last looked left no op
// either. It moved the row to its uid in the same transaction, so
// the row says which it was (`docs/BUGS.md` B176).
self.follow_landed_create(h);
if is_local_uid(&h.uid) {
debug!(uid = %h.uid, "local node was unlinked before creation; dropping revision");
let _ = std::fs::remove_file(&h.path);
return Ok(());
}
}
// Materialize as much of the full content as is free to materialize. A
// complete blob is uploadable without the network and lets a later write
@@ -8780,6 +8786,26 @@ mod tests {
assert_eq!(replaced.matches("self.withdraw_queued_ops(uid)").count(), 2);
}
#[test]
fn a_write_closed_as_its_create_lands_is_queued() {
// The release followed a landing create, found none, and the create
// landed before it asked for the create op. Finding none, it took the
// file for one unlinked before it was created and dropped the write
// (B176). The landing removes the op and moves the row at once, so a
// missing op is checked against the row.
let revision = function_source(include_str!("lib.rs"), "fn queue_revision(");
assert_before(
revision,
"self.db.has_create_op(",
"self.follow_landed_create(h)",
);
assert_before(
revision,
"self.follow_landed_create(h)",
"dropping revision",
);
}
#[test]
fn a_replaced_node_put_back_shows_in_the_mount_again() {
// The restore after a failed rename left the node hidden and out of