From 39cde9f9a3d67c7bd8d457e0f4c31294d1c4b78b Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:33:13 -0700 Subject: [PATCH] builder: retry cached archive renames on Windows (#5462) --- builder/library.go | 2 +- builder/robustio_other.go | 9 ++++++++ builder/robustio_windows.go | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 builder/robustio_other.go create mode 100644 builder/robustio_windows.go diff --git a/builder/library.go b/builder/library.go index 73fa2545f..1f8abce96 100644 --- a/builder/library.go +++ b/builder/library.go @@ -218,7 +218,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ return err } // Store this archive in the cache. - return os.Rename(f.Name(), archiveFilePath) + return robustRename(f.Name(), archiveFilePath) }, } diff --git a/builder/robustio_other.go b/builder/robustio_other.go new file mode 100644 index 000000000..fd939bffd --- /dev/null +++ b/builder/robustio_other.go @@ -0,0 +1,9 @@ +//go:build !windows + +package builder + +import "os" + +func robustRename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} diff --git a/builder/robustio_windows.go b/builder/robustio_windows.go new file mode 100644 index 000000000..22d5c2bab --- /dev/null +++ b/builder/robustio_windows.go @@ -0,0 +1,44 @@ +package builder + +import ( + "errors" + "math/rand" + "os" + "syscall" + "time" +) + +const robustRenameTimeout = 2 * time.Second + +func robustRename(oldpath, newpath string) error { + var bestErr error + start := time.Now() + nextSleep := time.Millisecond + for { + err := os.Rename(oldpath, newpath) + if err == nil || !isEphemeralRenameError(err) { + return err + } + if bestErr == nil { + bestErr = err + } + if d := time.Since(start) + nextSleep; d >= robustRenameTimeout { + return bestErr + } + time.Sleep(nextSleep) + nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) + } +} + +func isEphemeralRenameError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + switch errno { + case syscall.Errno(2), // ERROR_FILE_NOT_FOUND + syscall.Errno(5), // ERROR_ACCESS_DENIED + syscall.Errno(32): // ERROR_SHARING_VIOLATION + return true + } + } + return false +}