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  }