Source file src/cmd/internal/robustio/robustio_flaky.go

     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  		if errno, ok := errors.AsType[syscall.Errno](err); ok && (lowestErrno == 0 || errno < lowestErrno) {
    35  			bestErr = err
    36  			lowestErrno = errno
    37  		} else if bestErr == nil {
    38  			bestErr = err
    39  		}
    40  
    41  		if start.IsZero() {
    42  			start = time.Now()
    43  		} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
    44  			break
    45  		}
    46  		time.Sleep(nextSleep)
    47  		nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
    48  	}
    49  
    50  	return bestErr
    51  }
    52  
    53  // rename is like os.Rename, but retries ephemeral errors.
    54  //
    55  // On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
    56  // MOVEFILE_REPLACE_EXISTING.
    57  //
    58  // Windows also provides a different system call, ReplaceFile,
    59  // that provides similar semantics, but perhaps preserves more metadata. (The
    60  // documentation on the differences between the two is very sparse.)
    61  //
    62  // Empirical error rates with MoveFileEx are lower under modest concurrency, so
    63  // for now we're sticking with what the os package already provides.
    64  func rename(oldpath, newpath string) (err error) {
    65  	return retry(func() (err error, mayRetry bool) {
    66  		err = os.Rename(oldpath, newpath)
    67  		return err, isEphemeralError(err)
    68  	})
    69  }
    70  
    71  // readFile is like os.ReadFile, but retries ephemeral errors.
    72  func readFile(filename string) ([]byte, error) {
    73  	var b []byte
    74  	err := retry(func() (err error, mayRetry bool) {
    75  		b, err = os.ReadFile(filename)
    76  
    77  		// Unlike in rename, we do not retry errFileNotFound here: it can occur
    78  		// as a spurious error, but the file may also genuinely not exist, so the
    79  		// increase in robustness is probably not worth the extra latency.
    80  		return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
    81  	})
    82  	return b, err
    83  }
    84  
    85  func removeAll(path string) error {
    86  	return retry(func() (err error, mayRetry bool) {
    87  		err = os.RemoveAll(path)
    88  		return err, isEphemeralError(err)
    89  	})
    90  }
    91  

View as plain text