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.
5 // +build windows darwin
17 const arbitraryTimeout = 500 * time.Millisecond
19 const ERROR_SHARING_VIOLATION = 32
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 {
26 lowestErrno syscall.Errno
28 nextSleep time.Duration = 1 * time.Millisecond
32 if err == nil || !mayRetry {
36 if errno, ok := err.(syscall.Errno); ok && (lowestErrno == 0 || errno < lowestErrno) {
39 } else if bestErr == nil {
45 } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
49 nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
55 // rename is like os.Rename, but retries ephemeral errors.
57 // On windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
58 // MOVEFILE_REPLACE_EXISTING.
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.)
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)
73 // readFile is like ioutil.ReadFile, but retries ephemeral errors.
74 func readFile(filename string) ([]byte, error) {
76 err := retry(func() (err error, mayRetry bool) {
77 b, err = ioutil.ReadFile(filename)
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.
83 return err, isEphemeralError(err) && err != errFileNotFound
88 func removeAll(path string) error {
89 return retry(func() (err error, mayRetry bool) {
90 err = os.RemoveAll(path)
91 return err, isEphemeralError(err)