Skip to content

Commit d407a8c

Browse files
author
Bryan C. Mills
committed
testing: retry spurious errors from RemoveAll for temp directories
This works around what appears to be either a kernel bug or a Go runtime or syscall bug affecting certain Windows versions (possibly all pre-2016?). The retry loop is a simplified version of the one used in cmd/go/internal/robustio. We use the same 2-second arbitrary timeout as was used in that package, since it seems to be reliable in practice on the affected builders. (If it proves to be too short, we can lengthen it, within reason, in a followup CL.) Since this puts a higher-level workaround in place, we can also revert the lower-level workaround added to a specific test in CL 345670. This addresses the specific occurrences of the bug for users of (*testing.T).TempDir, but does not fix the underlying bug for Go users outside the "testing" package (which remains open as #25965). Fixes #50051 Updates #48012 Updates #25965 Change-Id: I35be7125f32f05c8350787f5ca9a22974b8d0770 Reviewed-on: https://go-review.googlesource.com/c/go/+/371296 Trust: Bryan Mills <[email protected]> Run-TryBot: Bryan Mills <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Patrik Nyblom <[email protected]> Trust: Patrik Nyblom <[email protected]> Run-TryBot: Patrik Nyblom <[email protected]>
1 parent 265fbaa commit d407a8c

File tree

4 files changed

+63
-1
lines changed

4 files changed

+63
-1
lines changed

src/runtime/syscall_windows_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ func TestSyscallN(t *testing.T) {
770770
for arglen := 0; arglen <= runtime.MaxArgs; arglen++ {
771771
arglen := arglen
772772
t.Run(fmt.Sprintf("arg-%d", arglen), func(t *testing.T) {
773+
t.Parallel()
773774
args := make([]string, arglen)
774775
rets := make([]string, arglen+1)
775776
params := make([]uintptr, arglen)

src/testing/testing.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,7 @@ func (c *common) TempDir() string {
10871087
c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern)
10881088
if c.tempDirErr == nil {
10891089
c.Cleanup(func() {
1090-
if err := os.RemoveAll(c.tempDir); err != nil {
1090+
if err := removeAll(c.tempDir); err != nil {
10911091
c.Errorf("TempDir RemoveAll cleanup: %v", err)
10921092
}
10931093
})
@@ -1106,6 +1106,36 @@ func (c *common) TempDir() string {
11061106
return dir
11071107
}
11081108

1109+
// removeAll is like os.RemoveAll, but retries Windows "Access is denied."
1110+
// errors up to an arbitrary timeout.
1111+
//
1112+
// Those errors have been known to occur spuriously on at least the
1113+
// windows-amd64-2012 builder (https://go.dev/issue/50051), and can only occur
1114+
// legitimately if the test leaves behind a temp file that either is still open
1115+
// or the test otherwise lacks permission to delete. In the case of legitimate
1116+
// failures, a failing test may take a bit longer to fail, but once the test is
1117+
// fixed the extra latency will go away.
1118+
func removeAll(path string) error {
1119+
const arbitraryTimeout = 2 * time.Second
1120+
var (
1121+
start time.Time
1122+
nextSleep = 1 * time.Millisecond
1123+
)
1124+
for {
1125+
err := os.RemoveAll(path)
1126+
if !isWindowsAccessDenied(err) {
1127+
return err
1128+
}
1129+
if start.IsZero() {
1130+
start = time.Now()
1131+
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
1132+
return err
1133+
}
1134+
time.Sleep(nextSleep)
1135+
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
1136+
}
1137+
}
1138+
11091139
// Setenv calls os.Setenv(key, value) and uses Cleanup to
11101140
// restore the environment variable to its original value
11111141
// after the test.

src/testing/testing_other.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2021 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
6+
7+
package testing
8+
9+
// isWindowsAccessDenied reports whether err is ERROR_ACCESS_DENIED,
10+
// which is defined only on Windows.
11+
func isWindowsAccessDenied(err error) bool {
12+
return false
13+
}

src/testing/testing_windows.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2021 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
6+
7+
package testing
8+
9+
import (
10+
"errors"
11+
"syscall"
12+
)
13+
14+
// isWindowsAccessDenied reports whether err is ERROR_ACCESS_DENIED,
15+
// which is defined only on Windows.
16+
func isWindowsAccessDenied(err error) bool {
17+
return errors.Is(err, syscall.ERROR_ACCESS_DENIED)
18+
}

0 commit comments

Comments
 (0)