whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,117 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// +build ignore
// The copyfiles script copies the contents of the internal cmd/go robustio
// package to the current directory, with adjustments to make it build.
//
// NOTE: In retrospect this script got out of hand, as we have to perform
// various operations on the package to get it to build at old Go versions. If
// in the future it proves to be flaky, delete it and just copy code manually.
package main
import (
"bytes"
"go/build/constraint"
"go/scanner"
"go/token"
"log"
"os"
"path/filepath"
"runtime"
"strings"
)
func main() {
dir := filepath.Join(runtime.GOROOT(), "src", "cmd", "go", "internal", "robustio")
entries, err := os.ReadDir(dir)
if err != nil {
log.Fatalf("reading the robustio dir: %v", err)
}
// Collect file content so that we can validate before copying.
fileContent := make(map[string][]byte)
windowsImport := []byte("\t\"internal/syscall/windows\"\n")
foundWindowsImport := false
for _, entry := range entries {
if strings.HasSuffix(entry.Name(), ".go") {
pth := filepath.Join(dir, entry.Name())
content, err := os.ReadFile(pth)
if err != nil {
log.Fatalf("reading %q: %v", entry.Name(), err)
}
// Replace the use of internal/syscall/windows.ERROR_SHARING_VIOLATION
// with a local constant.
if entry.Name() == "robustio_windows.go" && bytes.Contains(content, windowsImport) {
foundWindowsImport = true
content = bytes.Replace(content, windowsImport, nil, 1)
content = bytes.Replace(content, []byte("windows.ERROR_SHARING_VIOLATION"), []byte("ERROR_SHARING_VIOLATION"), -1)
}
// Replace os.ReadFile with os.ReadFile (for 1.15 and older). We
// attempt to match calls (via the '('), to avoid matching mentions of
// os.ReadFile in comments.
//
// TODO(rfindley): once we (shortly!) no longer support 1.15, remove
// this and break the build.
if bytes.Contains(content, []byte("os.ReadFile(")) {
content = bytes.Replace(content, []byte("\"os\""), []byte("\"io/ioutil\"\n\t\"os\""), 1)
content = bytes.Replace(content, []byte("os.ReadFile("), []byte("os.ReadFile("), -1)
}
// Add +build constraints, for 1.16.
content = addPlusBuildConstraints(content)
fileContent[entry.Name()] = content
}
}
if !foundWindowsImport {
log.Fatal("missing expected import of internal/syscall/windows in robustio_windows.go")
}
for name, content := range fileContent {
if err := os.WriteFile(name, content, 0644); err != nil {
log.Fatalf("writing %q: %v", name, err)
}
}
}
// addPlusBuildConstraints splices in +build constraints for go:build
// constraints encountered in the source.
//
// Gopls still builds at Go 1.16, which requires +build constraints.
func addPlusBuildConstraints(src []byte) []byte {
var s scanner.Scanner
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
s.Init(file, src, nil /* no error handler */, scanner.ScanComments)
result := make([]byte, 0, len(src))
lastInsertion := 0
for {
pos, tok, lit := s.Scan()
if tok == token.EOF {
break
}
if tok == token.COMMENT {
if c, err := constraint.Parse(lit); err == nil {
plusBuild, err := constraint.PlusBuildLines(c)
if err != nil {
log.Fatalf("computing +build constraint for %q: %v", lit, err)
}
insertAt := file.Offset(pos) + len(lit)
result = append(result, src[lastInsertion:insertAt]...)
result = append(result, []byte("\n"+strings.Join(plusBuild, "\n"))...)
lastInsertion = insertAt
}
}
}
result = append(result, src[lastInsertion:]...)
return result
}
@@ -0,0 +1,16 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package robustio
import "syscall"
// The robustio package is copied from cmd/go/internal/robustio, a package used
// by the go command to retry known flaky operations on certain operating systems.
//go:generate go run copyfiles.go
// Since the gopls module cannot access internal/syscall/windows, copy a
// necessary constant.
const ERROR_SHARING_VIOLATION syscall.Errno = 32
@@ -0,0 +1,69 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package robustio wraps I/O functions that are prone to failure on Windows,
// transparently retrying errors up to an arbitrary timeout.
//
// Errors are classified heuristically and retries are bounded, so the functions
// in this package do not completely eliminate spurious errors. However, they do
// significantly reduce the rate of failure in practice.
//
// If so, the error will likely wrap one of:
// The functions in this package do not completely eliminate spurious errors,
// but substantially reduce their rate of occurrence in practice.
package robustio
import "time"
// Rename is like os.Rename, but on Windows retries errors that may occur if the
// file is concurrently read or overwritten.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func Rename(oldpath, newpath string) error {
return rename(oldpath, newpath)
}
// ReadFile is like os.ReadFile, but on Windows retries errors that may
// occur if the file is concurrently replaced.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func ReadFile(filename string) ([]byte, error) {
return readFile(filename)
}
// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur
// if an executable file in the directory has recently been executed.
//
// (See golang.org/issue/19491.)
func RemoveAll(path string) error {
return removeAll(path)
}
// IsEphemeralError reports whether err is one of the errors that the functions
// in this package attempt to mitigate.
//
// Errors considered ephemeral include:
// - syscall.ERROR_ACCESS_DENIED
// - syscall.ERROR_FILE_NOT_FOUND
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
//
// This set may be expanded in the future; programs must not rely on the
// non-ephemerality of any given error.
func IsEphemeralError(err error) bool {
return isEphemeralError(err)
}
// A FileID uniquely identifies a file in the file system.
//
// If GetFileID(name1) returns the same ID as GetFileID(name2), the two file
// names denote the same file.
// A FileID is comparable, and thus suitable for use as a map key.
type FileID struct {
device, inode uint64
}
// GetFileID returns the file system's identifier for the file, and its
// modification time.
// Like os.Stat, it reads through symbolic links.
func GetFileID(filename string) (FileID, time.Time, error) { return getFileID(filename) }
@@ -0,0 +1,21 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package robustio
import (
"errors"
"syscall"
)
const errFileNotFound = syscall.ENOENT
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
return errno == errFileNotFound
}
return false
}
@@ -0,0 +1,92 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows || darwin
// +build windows darwin
package robustio
import (
"errors"
"math/rand"
"os"
"syscall"
"time"
)
const arbitraryTimeout = 2000 * time.Millisecond
// retry retries ephemeral errors from f up to an arbitrary timeout
// to work around filesystem flakiness on Windows and Darwin.
func retry(f func() (err error, mayRetry bool)) error {
var (
bestErr error
lowestErrno syscall.Errno
start time.Time
nextSleep time.Duration = 1 * time.Millisecond
)
for {
err, mayRetry := f()
if err == nil || !mayRetry {
return err
}
var errno syscall.Errno
if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) {
bestErr = err
lowestErrno = errno
} else if bestErr == nil {
bestErr = err
}
if start.IsZero() {
start = time.Now()
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
break
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
return bestErr
}
// rename is like os.Rename, but retries ephemeral errors.
//
// On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
// MOVEFILE_REPLACE_EXISTING.
//
// Windows also provides a different system call, ReplaceFile,
// that provides similar semantics, but perhaps preserves more metadata. (The
// documentation on the differences between the two is very sparse.)
//
// Empirical error rates with MoveFileEx are lower under modest concurrency, so
// for now we're sticking with what the os package already provides.
func rename(oldpath, newpath string) (err error) {
return retry(func() (err error, mayRetry bool) {
err = os.Rename(oldpath, newpath)
return err, isEphemeralError(err)
})
}
// readFile is like os.ReadFile, but retries ephemeral errors.
func readFile(filename string) ([]byte, error) {
var b []byte
err := retry(func() (err error, mayRetry bool) {
b, err = os.ReadFile(filename)
// Unlike in rename, we do not retry errFileNotFound here: it can occur
// as a spurious error, but the file may also genuinely not exist, so the
// increase in robustness is probably not worth the extra latency.
return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
})
return b, err
}
func removeAll(path string) error {
return retry(func() (err error, mayRetry bool) {
err = os.RemoveAll(path)
return err, isEphemeralError(err)
})
}
@@ -0,0 +1,28 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !windows && !darwin
// +build !windows,!darwin
package robustio
import (
"os"
)
func rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
func readFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}
func removeAll(path string) error {
return os.RemoveAll(path)
}
func isEphemeralError(err error) bool {
return false
}
@@ -0,0 +1,26 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build plan9
// +build plan9
package robustio
import (
"os"
"syscall"
"time"
)
func getFileID(filename string) (FileID, time.Time, error) {
fi, err := os.Stat(filename)
if err != nil {
return FileID{}, time.Time{}, err
}
dir := fi.Sys().(*syscall.Dir)
return FileID{
device: uint64(dir.Type)<<32 | uint64(dir.Dev),
inode: dir.Qid.Path,
}, fi.ModTime(), nil
}
@@ -0,0 +1,28 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !windows && !plan9
// +build !windows,!plan9
// TODO(adonovan): use 'unix' tag when go1.19 can be assumed.
package robustio
import (
"os"
"syscall"
"time"
)
func getFileID(filename string) (FileID, time.Time, error) {
fi, err := os.Stat(filename)
if err != nil {
return FileID{}, time.Time{}, err
}
stat := fi.Sys().(*syscall.Stat_t)
return FileID{
device: uint64(stat.Dev), // (int32 on darwin, uint64 on linux)
inode: stat.Ino,
}, fi.ModTime(), nil
}
@@ -0,0 +1,101 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package robustio_test
import (
"os"
"path/filepath"
"runtime"
"testing"
"time"
"golang.org/x/tools/internal/robustio"
)
func checkOSLink(t *testing.T, err error) {
if err == nil {
return
}
t.Helper()
switch runtime.GOOS {
case "aix", "darwin", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "solaris":
// Non-mobile OS known to always support os.Symlink and os.Link.
t.Fatal(err)
default:
t.Skipf("skipping due to error on %v: %v", runtime.GOOS, err)
}
}
func TestFileInfo(t *testing.T) {
// A nonexistent file has no ID.
nonexistent := filepath.Join(t.TempDir(), "nonexistent")
if _, _, err := robustio.GetFileID(nonexistent); err == nil {
t.Fatalf("GetFileID(nonexistent) succeeded unexpectedly")
}
// A regular file has an ID.
real := filepath.Join(t.TempDir(), "real")
if err := os.WriteFile(real, nil, 0644); err != nil {
t.Fatalf("can't create regular file: %v", err)
}
realID, realMtime, err := robustio.GetFileID(real)
if err != nil {
t.Fatalf("can't get ID of regular file: %v", err)
}
// Sleep so that we get a new mtime for subsequent writes.
time.Sleep(2 * time.Second)
// A second regular file has a different ID.
real2 := filepath.Join(t.TempDir(), "real2")
if err := os.WriteFile(real2, nil, 0644); err != nil {
t.Fatalf("can't create second regular file: %v", err)
}
real2ID, real2Mtime, err := robustio.GetFileID(real2)
if err != nil {
t.Fatalf("can't get ID of second regular file: %v", err)
}
if realID == real2ID {
t.Errorf("realID %+v == real2ID %+v", realID, real2ID)
}
if realMtime.Equal(real2Mtime) {
t.Errorf("realMtime %v == real2Mtime %v", realMtime, real2Mtime)
}
// A symbolic link has the same ID as its target.
t.Run("symlink", func(t *testing.T) {
symlink := filepath.Join(t.TempDir(), "symlink")
checkOSLink(t, os.Symlink(real, symlink))
symlinkID, symlinkMtime, err := robustio.GetFileID(symlink)
if err != nil {
t.Fatalf("can't get ID of symbolic link: %v", err)
}
if realID != symlinkID {
t.Errorf("realID %+v != symlinkID %+v", realID, symlinkID)
}
if !realMtime.Equal(symlinkMtime) {
t.Errorf("realMtime %v != symlinkMtime %v", realMtime, symlinkMtime)
}
})
// Two hard-linked files have the same ID.
t.Run("hardlink", func(t *testing.T) {
hardlink := filepath.Join(t.TempDir(), "hardlink")
checkOSLink(t, os.Link(real, hardlink))
hardlinkID, hardlinkMtime, err := robustio.GetFileID(hardlink)
if err != nil {
t.Fatalf("can't get ID of hard link: %v", err)
}
if realID != hardlinkID {
t.Errorf("realID %+v != hardlinkID %+v", realID, hardlinkID)
}
if !realMtime.Equal(hardlinkMtime) {
t.Errorf("realMtime %v != hardlinkMtime %v", realMtime, hardlinkMtime)
}
})
}
@@ -0,0 +1,51 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package robustio
import (
"errors"
"syscall"
"time"
)
const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
switch errno {
case syscall.ERROR_ACCESS_DENIED,
syscall.ERROR_FILE_NOT_FOUND,
ERROR_SHARING_VIOLATION:
return true
}
}
return false
}
// Note: it may be convenient to have this helper return fs.FileInfo, but
// implementing this is actually quite involved on Windows. Since we only
// currently use mtime, keep it simple.
func getFileID(filename string) (FileID, time.Time, error) {
filename16, err := syscall.UTF16PtrFromString(filename)
if err != nil {
return FileID{}, time.Time{}, err
}
h, err := syscall.CreateFile(filename16, 0, 0, nil, syscall.OPEN_EXISTING, uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS), 0)
if err != nil {
return FileID{}, time.Time{}, err
}
defer syscall.CloseHandle(h)
var i syscall.ByHandleFileInformation
if err := syscall.GetFileInformationByHandle(h, &i); err != nil {
return FileID{}, time.Time{}, err
}
mtime := time.Unix(0, i.LastWriteTime.Nanoseconds())
return FileID{
device: uint64(i.VolumeSerialNumber),
inode: uint64(i.FileIndexHigh)<<32 | uint64(i.FileIndexLow),
}, mtime, nil
}