Skip to content

Commit 2cb1091

Browse files
committed
Propose allowing unlink for LocalUnopened files
Files created but never opened (LocalUnopened) have no S3 state and can be unlinked. This prevents files from getting stuck in a state where they cannot be deleted. Signed-off-by: Maksim Beiner <maksim-beiner@idexx.com>
1 parent 8adc754 commit 2cb1091

File tree

2 files changed

+49
-30
lines changed

2 files changed

+49
-30
lines changed

mountpoint-s3-fs/src/superblock.rs

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -608,22 +608,34 @@ impl<OC: ObjectClient + Send + Sync + Clone> Metablock for Superblock<OC> {
608608
return Err(InodeError::IsDirectory(inode.err()));
609609
}
610610

611-
let write_status = {
612-
let inode_state = inode.get_inode_state()?;
613-
inode_state.write_status
614-
};
611+
let inode_state = inode.get_mut_inode_state()?;
615612

616-
match write_status {
617-
WriteStatus::LocalUnopened | WriteStatus::LocalOpen | WriteStatus::PendingRename => {
613+
match inode_state.write_status {
614+
WriteStatus::LocalOpen | WriteStatus::PendingRename => {
618615
// In the future, we may permit `unlink` and cancel any in-flight uploads.
619616
warn!(
620617
parent = parent_ino,
621618
?name,
619+
write_status = ?inode_state.write_status,
622620
"unlink on local file not allowed until write is complete",
623621
);
624622
return Err(InodeError::UnlinkNotPermittedWhileWriting(inode.err()));
625623
}
624+
WriteStatus::LocalUnopened => {
625+
debug!(
626+
parent = parent_ino,
627+
?name,
628+
"unlink on LocalUnopened file, no s3 cleanup needed",
629+
);
630+
631+
let mut parent_state = parent.get_mut_inode_state()?;
632+
parent_state.kind_data.remove_child(inode.name(), inode.ino());
633+
634+
return Ok(());
635+
}
626636
WriteStatus::Remote => {
637+
drop(inode_state);
638+
627639
let bucket = &self.inner.s3_path.bucket;
628640
let s3_key = self.inner.full_key_for_inode(&inode);
629641
debug!(parent=?parent_ino, ?name, "unlink on remote file will delete key {}", s3_key);
@@ -647,30 +659,7 @@ impl<OC: ObjectClient + Send + Sync + Clone> Metablock for Superblock<OC> {
647659
// The Linux Kernel's VFS should lock both the parent and child (https://www.kernel.org/doc/html/next/filesystems/directory-locking.html).
648660
// However, the superblock is still subject to concurrent changes as we don't hold this lock over remote calls.
649661
let mut parent_state = parent.get_mut_inode_state()?;
650-
match &mut parent_state.kind_data {
651-
InodeKindData::File { .. } => {
652-
debug_assert!(false, "inodes never change kind");
653-
return Err(InodeError::NotADirectory(parent.err()));
654-
}
655-
InodeKindData::Directory { children, .. } => {
656-
// We want to remove the original child.
657-
// If there is a mismatch in inode number between the existing inode and the deleted inode, we invalidate the existing inode's stat.
658-
// If for some reason the child with the specified name was already removed, we do nothing as this is the desired state.
659-
if let Some(existing_inode) = children.get(inode.name()) {
660-
if existing_inode.ino() == inode.ino() {
661-
children.remove(inode.name());
662-
} else {
663-
// Mismatch in inode number, thus the existing inode might not be the same one we deleted
664-
let mut state = existing_inode.get_mut_inode_state_no_check();
665-
666-
// Invalidate the inode's stats so we refresh them from S3 when next queried
667-
state.stat.update_validity(Duration::from_secs(0));
668-
}
669-
} else {
670-
debug!("parent did not contain child after deletion during unlink");
671-
}
672-
}
673-
};
662+
parent_state.kind_data.remove_child(inode.name(), inode.ino());
674663

675664
Ok(())
676665
}

mountpoint-s3-fs/src/superblock/inode.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,36 @@ impl InodeKindData {
286286
},
287287
}
288288
}
289+
290+
pub fn remove_child(&mut self, name: &str, expected_ino: InodeNo) {
291+
match self {
292+
InodeKindData::File {} => {
293+
debug_assert!(false, "remove_child called on a file, not a directory");
294+
}
295+
InodeKindData::Directory {
296+
children,
297+
writing_children,
298+
..
299+
} => {
300+
// We want to remove the original child.
301+
// If there is a mismatch in inode number between the existing inode and the deleted inode, we invalidate the existing inode's stat.
302+
// If for some reason the child with the specified name was already removed, we do nothing as this is the desired state.
303+
if let Some(existing_inode) = children.get(name) {
304+
if existing_inode.ino() == expected_ino {
305+
children.remove(name);
306+
writing_children.remove(&expected_ino);
307+
} else {
308+
// Mismatch in inode number, thus the existing inode might not be the same one we deleted
309+
let mut state = existing_inode.get_mut_inode_state_no_check();
310+
// Invalidate the inode's stats so we refresh them from S3 when next queried
311+
state.stat.update_validity(Duration::from_secs(0));
312+
}
313+
} else {
314+
debug!("parent did not contain child after deletion during unlink");
315+
}
316+
}
317+
}
318+
}
289319
}
290320

291321
/// Inode write status (local vs remote)

0 commit comments

Comments
 (0)