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