From ef28f49ec34452f181499820d43e4ffc0cb38e1a Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 15 Dec 2025 22:38:28 -0800 Subject: [PATCH] fix: correctly detect missing source file during volume copy (#7784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: correctly detect missing source file during volume copy The previous fix (commit 5c27522) incorrectly used progressedBytes == 0 to detect if the source file didn't exist. This was wrong because it would also delete files when the source file exists but is empty. This fix: 1. Server side: Send ModifiedTsNs even for empty files, so the client knows the source file exists 2. Client side: Check modifiedTsNs == 0 instead of progressedBytes == 0 to determine if source file didn't exist Now the logic correctly handles: - Source file doesn't exist → No ModifiedTsNs sent → Remove empty file - Source file exists but is empty → ModifiedTsNs sent → Keep empty file - Source file exists with content → ModifiedTsNs sent → Keep file with content Fixes #7777 * Update weed/server/volume_grpc_copy.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- weed/server/volume_grpc_copy.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/weed/server/volume_grpc_copy.go b/weed/server/volume_grpc_copy.go index 80c32d73e..c48f3edf2 100644 --- a/weed/server/volume_grpc_copy.go +++ b/weed/server/volume_grpc_copy.go @@ -295,9 +295,11 @@ func writeToFile(client volume_server_pb.VolumeServer_CopyFileClient, fileName s } wt.MaybeSlowdown(int64(len(resp.FileContent))) } - // If no data was written (source file was not found), remove the empty file - // to avoid leaving corrupted empty files that cause parse errors later - if progressedBytes == 0 && !isAppend { + // If we never received a modifiedTsNs, it means the source file did not exist. + // Remove the empty file we created to avoid leaving corrupted empty files. + // Note: We check modifiedTsNs (not progressedBytes) because an empty source file + // is valid and should result in an empty destination file. + if modifiedTsNs == 0 && !isAppend { if removeErr := os.Remove(fileName); removeErr != nil { glog.V(1).Infof("failed to remove empty file %s: %v", fileName, removeErr) } else { @@ -426,6 +428,19 @@ func (vs *VolumeServer) CopyFile(req *volume_server_pb.CopyFileRequest, stream v } +// If no data has been sent in the loop (e.g. for an empty file, or when stopOffset is 0), +// we still need to send the ModifiedTsNs so the client knows the source file exists. +// fileModTsNs is set to 0 after the first send, so if it's still non-zero, +// we haven't sent anything yet. + if fileModTsNs != 0 { + err = stream.Send(&volume_server_pb.CopyFileResponse{ + ModifiedTsNs: fileModTsNs, + }) + if err != nil { + return err + } + } + return nil }