...

Source file src/cmd/go/internal/robustio/robustio_windows.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	package robustio
     6	
     7	import (
     8		"errors"
     9		"internal/syscall/windows"
    10		"io/ioutil"
    11		"math/rand"
    12		"os"
    13		"syscall"
    14		"time"
    15	)
    16	
    17	const arbitraryTimeout = 500 * time.Millisecond
    18	
    19	// retry retries ephemeral errors from f up to an arbitrary timeout
    20	// to work around spurious filesystem errors on Windows
    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	// 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 ioutil.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 = ioutil.ReadFile(filename)
    77	
    78			// Unlike in rename, we do not retry ERROR_FILE_NOT_FOUND 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, syscall.ERROR_FILE_NOT_FOUND)
    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	}
    92	
    93	// isEphemeralError returns true if err may be resolved by waiting.
    94	func isEphemeralError(err error) bool {
    95		var errno syscall.Errno
    96		if errors.As(err, &errno) {
    97			switch errno {
    98			case syscall.ERROR_ACCESS_DENIED,
    99				syscall.ERROR_FILE_NOT_FOUND,
   100				windows.ERROR_SHARING_VIOLATION:
   101				return true
   102			}
   103		}
   104		return false
   105	}
   106	

View as plain text