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