golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/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  	"math/rand"
    13  	"os"
    14  	"syscall"
    15  	"time"
    16  )
    17  
    18  const arbitraryTimeout = 2000 * time.Millisecond
    19  
    20  // retry retries ephemeral errors from f up to an arbitrary timeout
    21  // to work around filesystem flakiness on Windows and Darwin.
    22  func retry(f func() (err error, mayRetry bool)) error {
    23  	var (
    24  		bestErr     error
    25  		lowestErrno syscall.Errno
    26  		start       time.Time
    27  		nextSleep   time.Duration = 1 * time.Millisecond
    28  	)
    29  	for {
    30  		err, mayRetry := f()
    31  		if err == nil || !mayRetry {
    32  			return err
    33  		}
    34  
    35  		var errno syscall.Errno
    36  		if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) {
    37  			bestErr = err
    38  			lowestErrno = errno
    39  		} else if bestErr == nil {
    40  			bestErr = err
    41  		}
    42  
    43  		if start.IsZero() {
    44  			start = time.Now()
    45  		} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
    46  			break
    47  		}
    48  		time.Sleep(nextSleep)
    49  		nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
    50  	}
    51  
    52  	return bestErr
    53  }
    54  
    55  // rename is like os.Rename, but retries ephemeral errors.
    56  //
    57  // On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
    58  // MOVEFILE_REPLACE_EXISTING.
    59  //
    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.)
    63  //
    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)
    70  	})
    71  }
    72  
    73  // readFile is like os.ReadFile, but retries ephemeral errors.
    74  func readFile(filename string) ([]byte, error) {
    75  	var b []byte
    76  	err := retry(func() (err error, mayRetry bool) {
    77  		b, err = os.ReadFile(filename)
    78  
    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.
    82  		return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
    83  	})
    84  	return b, err
    85  }
    86  
    87  func removeAll(path string) error {
    88  	return retry(func() (err error, mayRetry bool) {
    89  		err = os.RemoveAll(path)
    90  		return err, isEphemeralError(err)
    91  	})
    92  }