435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build windows darwin
|
|
|
|
package robustio
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"os"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
const arbitraryTimeout = 500 * time.Millisecond
|
|
|
|
const ERROR_SHARING_VIOLATION = 32
|
|
|
|
// retry retries ephemeral errors from f up to an arbitrary timeout
|
|
// to work around filesystem flakiness on Windows and Darwin.
|
|
func retry(f func() (err error, mayRetry bool)) error {
|
|
var (
|
|
bestErr error
|
|
lowestErrno syscall.Errno
|
|
start time.Time
|
|
nextSleep time.Duration = 1 * time.Millisecond
|
|
)
|
|
for {
|
|
err, mayRetry := f()
|
|
if err == nil || !mayRetry {
|
|
return err
|
|
}
|
|
|
|
if errno, ok := err.(syscall.Errno); ok && (lowestErrno == 0 || errno < lowestErrno) {
|
|
bestErr = err
|
|
lowestErrno = errno
|
|
} else if bestErr == nil {
|
|
bestErr = err
|
|
}
|
|
|
|
if start.IsZero() {
|
|
start = time.Now()
|
|
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
|
|
break
|
|
}
|
|
time.Sleep(nextSleep)
|
|
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
|
|
}
|
|
|
|
return bestErr
|
|
}
|
|
|
|
// rename is like os.Rename, but retries ephemeral errors.
|
|
//
|
|
// On windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
|
|
// MOVEFILE_REPLACE_EXISTING.
|
|
//
|
|
// Windows also provides a different system call, ReplaceFile,
|
|
// that provides similar semantics, but perhaps preserves more metadata. (The
|
|
// documentation on the differences between the two is very sparse.)
|
|
//
|
|
// Empirical error rates with MoveFileEx are lower under modest concurrency, so
|
|
// for now we're sticking with what the os package already provides.
|
|
func rename(oldpath, newpath string) (err error) {
|
|
return retry(func() (err error, mayRetry bool) {
|
|
err = os.Rename(oldpath, newpath)
|
|
return err, isEphemeralError(err)
|
|
})
|
|
}
|
|
|
|
// readFile is like ioutil.ReadFile, but retries ephemeral errors.
|
|
func readFile(filename string) ([]byte, error) {
|
|
var b []byte
|
|
err := retry(func() (err error, mayRetry bool) {
|
|
b, err = ioutil.ReadFile(filename)
|
|
|
|
// Unlike in rename, we do not retry errFileNotFound here: it can occur
|
|
// as a spurious error, but the file may also genuinely not exist, so the
|
|
// increase in robustness is probably not worth the extra latency.
|
|
|
|
return err, isEphemeralError(err) && err != errFileNotFound
|
|
})
|
|
return b, err
|
|
}
|
|
|
|
func removeAll(path string) error {
|
|
return retry(func() (err error, mayRetry bool) {
|
|
err = os.RemoveAll(path)
|
|
return err, isEphemeralError(err)
|
|
})
|
|
}
|