diff --git a/weed/mount/meta_cache/meta_cache.go b/weed/mount/meta_cache/meta_cache.go index d5f89c7294..e4c16979df 100644 --- a/weed/mount/meta_cache/meta_cache.go +++ b/weed/mount/meta_cache/meta_cache.go @@ -937,6 +937,9 @@ type EntryInvalidation struct { // once the path is empty, so this is the only thing left saying whether a // directory or a file went away. WasDirectory bool + // LiveHardLinkId is set on a delete that left other names holding the + // hard-linked file, by the count the filer kept its chunks on. + LiveHardLinkId []byte } type metadataResponseSideEffects struct { @@ -1440,7 +1443,11 @@ func collectEntryInvalidations(resp *filer_pb.SubscribeMetadataResponse) []Entry if filer_pb.IsDelete(resp) && message.OldEntry != nil { oldKey := util.NewFullPath(resp.Directory, message.OldEntry.Name) - invalidations = append(invalidations, EntryInvalidation{Path: oldKey, TsNs: resp.TsNs, Deleted: true, Signatures: signatures, WasDirectory: message.OldEntry.IsDirectory}) + invalidation := EntryInvalidation{Path: oldKey, TsNs: resp.TsNs, Deleted: true, Signatures: signatures, WasDirectory: message.OldEntry.IsDirectory} + if len(message.OldEntry.HardLinkId) > 0 && message.OldEntry.HardLinkCounter > 1 { + invalidation.LiveHardLinkId = message.OldEntry.HardLinkId + } + invalidations = append(invalidations, invalidation) } return invalidations diff --git a/weed/mount/weedfs.go b/weed/mount/weedfs.go index 5cd89e448a..4f528a070c 100644 --- a/weed/mount/weedfs.go +++ b/weed/mount/weedfs.go @@ -1023,6 +1023,12 @@ func (wfs *WFS) invalidateOpenFileHandle(invalidation meta_cache.EntryInvalidati // so no flush recreates the unlinked name. Either way the entry and // dirty pages stay, so the open fd still reads its buffered writes. if invalidation.Deleted { + // The next flush reads the survivor's stored entry before + // publishing, so the version stays where it was. + if len(invalidation.LiveHardLinkId) > 0 && wfs.followSurvivingLink(fh, inode, filePath) { + fh.linkRefresh = invalidation.LiveHardLinkId + return + } fh.isDeleted = true fh.deleteEpoch++ } @@ -1061,6 +1067,24 @@ func (wfs *WFS) invalidateOpenFileHandle(invalidation meta_cache.EntryInvalidati return } +// followSurvivingLink drops a hard link name another client deleted and +// keeps the handle on a name of the inode this mount still knows, reporting +// false when it knows none. +func (wfs *WFS) followSurvivingLink(fh *FileHandle, inode uint64, removed util.FullPath) bool { + survives := false + for _, p := range wfs.inodeToPath.GetAllPaths(inode) { + survives = survives || p != removed + } + if !survives { + return false + } + wfs.inodeToPath.RemovePath(removed, nil) + if survivor, status := wfs.inodeToPath.GetPath(inode); status == fuse.OK { + fh.RememberPath(survivor) + } + return true +} + func (wfs *WFS) LookupFn() wdclient.LookupFileIdFunctionType { if wfs.option.VolumeServerAccess == "filerProxy" { return func(ctx context.Context, fileId string) (targetUrls []string, err error) {