From dd27c6cd274675e563517589c5083ce1e9478c4c Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Thu, 30 Apr 2026 08:19:14 -0700 Subject: [PATCH] fix: scoutfs multipart alignment check for last part The MoveData() requires that all but the last part be 4k aligned. We accidentally were including the alignment check for the last part causing large uploads where the total object was not a multiple of 4k to fallback to copying the last part. For very large part sizes this was triggering timeouts in some clients. --- backend/scoutfs/scoutfs_compat.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/scoutfs/scoutfs_compat.go b/backend/scoutfs/scoutfs_compat.go index 1776df77..4afb171f 100644 --- a/backend/scoutfs/scoutfs_compat.go +++ b/backend/scoutfs/scoutfs_compat.go @@ -270,6 +270,8 @@ func (s *ScoutFS) UploadPart(ctx context.Context, input *s3.UploadPartInput) (*s }) } +const scoutfsBlockSize = 4096 + // CompleteMultipartUpload scoutfs complete upload uses scoutfs move blocks // ioctl to not have to read and copy the part data to the final object. This // saves a read and write cycle for all mutlipart uploads. @@ -279,9 +281,16 @@ func (s *ScoutFS) CompleteMultipartUpload(ctx context.Context, input *s3.Complet acct = auth.Account{} } + totalParts := len(input.MultipartUpload.Parts) + callCount := 0 + return s.Posix.CompleteMultipartUploadWithCopy(ctx, input, func(from *os.File, to *os.File) (bool, error) { - // May fail if the files are not 4K aligned; check for alignment + callCount++ + isLast := callCount == totalParts + + // May fail if the files are not 4K aligned; check for alignment. + // The last part is allowed to have a size that is not a multiple of 4K. ffi, err := from.Stat() if err != nil { return true, fmt.Errorf("complete-mpu stat from: %w", err) @@ -290,7 +299,7 @@ func (s *ScoutFS) CompleteMultipartUpload(ctx context.Context, input *s3.Complet if err != nil { return true, fmt.Errorf("complete-mpu stat to: %w", err) } - if ffi.Size()%4096 != 0 || tfi.Size()%4096 != 0 { + if !isLast && (ffi.Size()%scoutfsBlockSize != 0 || tfi.Size()%scoutfsBlockSize != 0) { return true, os.ErrInvalid }