github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/robustio/robustio_flaky.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build windows || darwin 6 7 package robustio 8 9 import ( 10 "errors" 11 "math/rand" 12 "os" 13 "syscall" 14 "time" 15 ) 16 17 const arbitraryTimeout = 2000 * time.Millisecond 18 19 // retry retries ephemeral errors from f up to an arbitrary timeout 20 // to work around filesystem flakiness on Windows and Darwin. 21 func retry(f func() (err error, mayRetry bool)) error { 22 var ( 23 bestErr error 24 lowestErrno syscall.Errno 25 start time.Time 26 nextSleep time.Duration = 1 * time.Millisecond 27 ) 28 for { 29 err, mayRetry := f() 30 if err == nil || !mayRetry { 31 return err 32 } 33 34 var errno syscall.Errno 35 if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) { 36 bestErr = err 37 lowestErrno = errno 38 } else if bestErr == nil { 39 bestErr = err 40 } 41 42 if start.IsZero() { 43 start = time.Now() 44 } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout { 45 break 46 } 47 time.Sleep(nextSleep) 48 nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) 49 } 50 51 return bestErr 52 } 53 54 // rename is like os.Rename, but retries ephemeral errors. 55 // 56 // On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with 57 // MOVEFILE_REPLACE_EXISTING. 58 // 59 // Windows also provides a different system call, ReplaceFile, 60 // that provides similar semantics, but perhaps preserves more metadata. (The 61 // documentation on the differences between the two is very sparse.) 62 // 63 // Empirical error rates with MoveFileEx are lower under modest concurrency, so 64 // for now we're sticking with what the os package already provides. 65 func rename(oldpath, newpath string) (err error) { 66 return retry(func() (err error, mayRetry bool) { 67 err = os.Rename(oldpath, newpath) 68 return err, isEphemeralError(err) 69 }) 70 } 71 72 // readFile is like os.ReadFile, but retries ephemeral errors. 73 func readFile(filename string) ([]byte, error) { 74 var b []byte 75 err := retry(func() (err error, mayRetry bool) { 76 b, err = os.ReadFile(filename) 77 78 // Unlike in rename, we do not retry errFileNotFound here: it can occur 79 // as a spurious error, but the file may also genuinely not exist, so the 80 // increase in robustness is probably not worth the extra latency. 81 return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound) 82 }) 83 return b, err 84 } 85 86 func removeAll(path string) error { 87 return retry(func() (err error, mayRetry bool) { 88 err = os.RemoveAll(path) 89 return err, isEphemeralError(err) 90 }) 91 }