src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

robustio_flaky.go (2540B)


      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 }