From a60d6a7faa86e17de5e9e708602fa73a1fc8850e Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Sat, 3 May 2025 09:30:45 -0700 Subject: [PATCH] fix: scoutfs racing mutlipart uploads internal error When multiple uploads with the same object key are racing, we can end up with an EEXIST when trying to link the final object into the namespace. When this happens, we should just remove the existing file and try again since the semantics are that the last upload should win. --- backend/scoutfs/scoutfs_compat.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/backend/scoutfs/scoutfs_compat.go b/backend/scoutfs/scoutfs_compat.go index 044acc7..0a590ba 100644 --- a/backend/scoutfs/scoutfs_compat.go +++ b/backend/scoutfs/scoutfs_compat.go @@ -155,10 +155,20 @@ func (tmp *tmpfile) link() error { } defer dirf.Close() - err = unix.Linkat(int(procdir.Fd()), filepath.Base(tmp.f.Name()), - int(dirf.Fd()), filepath.Base(objPath), unix.AT_SYMLINK_FOLLOW) - if err != nil { - return fmt.Errorf("link tmpfile: %w", err) + for { + err = unix.Linkat(int(procdir.Fd()), filepath.Base(tmp.f.Name()), + int(dirf.Fd()), filepath.Base(objPath), unix.AT_SYMLINK_FOLLOW) + if errors.Is(err, fs.ErrExist) { + err := os.Remove(objPath) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("remove stale path: %w", err) + } + continue + } + if err != nil { + return fmt.Errorf("link tmpfile: %w", err) + } + break } err = tmp.f.Close()