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