From 288ba5fec8364ccf4e1d3d9323b9e12828458e21 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 28 Dec 2025 23:22:13 -0800 Subject: [PATCH] mount: let filer handle chunk deletion decision (#7900) * mount: let filer handle chunk deletion decision Remove chunk deletion decision from FUSE mount's Unlink operation. Previously, the mount decided whether to delete chunks based on its locally cached entry's HardLinkCounter, which could be stale. Now always pass isDeleteData=true and let the filer make the authoritative decision based on its own data. This prevents potential inconsistencies when: - The FUSE mount's cached entry is stale - Race conditions occur between multiple mounts - Direct filer operations change hard link counts * filer: check hard link counter before deleting chunks When deleting an entry, only delete the underlying chunks if: 1. It is not a hard link 2. OR it is the last hard link (counter <= 1) This protects against data loss when a client (like FUSE mount) requests chunk deletion for a file that has multiple hard links. --- weed/filer/filer_delete_entry.go | 6 +++++- weed/mount/weedfs_file_mkrm.go | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/weed/filer/filer_delete_entry.go b/weed/filer/filer_delete_entry.go index cfe07ec5a..8b90f8c00 100644 --- a/weed/filer/filer_delete_entry.go +++ b/weed/filer/filer_delete_entry.go @@ -53,7 +53,11 @@ func (f *Filer) DeleteEntryMetaAndData(ctx context.Context, p util.FullPath, isR } if shouldDeleteChunks && !isDeleteCollection { - f.DeleteChunks(ctx, p, entry.GetChunks()) + if len(entry.HardLinkId) != 0 && entry.HardLinkCounter > 1 { + // if the file is a hard link and there are other hard links, do not delete the chunks + } else { + f.DeleteChunks(ctx, p, entry.GetChunks()) + } } if isDeleteCollection { diff --git a/weed/mount/weedfs_file_mkrm.go b/weed/mount/weedfs_file_mkrm.go index 5734e9df8..4fcaea4fa 100644 --- a/weed/mount/weedfs_file_mkrm.go +++ b/weed/mount/weedfs_file_mkrm.go @@ -140,8 +140,9 @@ func (wfs *WFS) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name strin // first, ensure the filer store can correctly delete glog.V(3).Infof("remove file: %v", entryFullPath) - isDeleteData := entry != nil && entry.HardLinkCounter <= 1 - err := filer_pb.Remove(context.Background(), wfs, string(dirFullPath), name, isDeleteData, false, false, false, []int32{wfs.signature}) + // Always let the filer decide whether to delete chunks based on its authoritative data. + // The filer has the correct hard link count and will only delete chunks when appropriate. + err := filer_pb.Remove(context.Background(), wfs, string(dirFullPath), name, true, false, false, false, []int32{wfs.signature}) if err != nil { glog.V(0).Infof("remove %s: %v", entryFullPath, err) return fuse.OK