Skip to content

Commit bb0c14b

Browse files
committed
os: don't fallback to the Stat slow path if file doesn't exist on Windows
os.Stat and os.Lstat first try stating the file without opening it. If that fails, then they open the file and try again, operations that tends to be slow. There is no point in trying the slow path if the file doesn't exist, we should just return an error immediately. This CL makes stating a non-existent file on Windows 50% faster: goos: windows goarch: amd64 pkg: os cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ StatNotExist-12 43.65µ ± 15% 20.02µ ± 10% -54.14% (p=0.000 n=10+7) │ old.txt │ new.txt │ │ B/op │ B/op vs base │ StatNotExist-12 224.0 ± 0% 224.0 ± 0% ~ (p=1.000 n=10+7) ¹ ¹ all samples are equal │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ StatNotExist-12 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10+7) ¹ Updates #72992. Change-Id: Iaeb9596d0d18e5a5a1bd1970e296a3480501af78 Reviewed-on: https://go-review.googlesource.com/c/go/+/671458 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jake Bailey <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent 3be537e commit bb0c14b

File tree

2 files changed

+19
-0
lines changed

2 files changed

+19
-0
lines changed

src/os/stat_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,18 @@ func TestClosedStat(t *testing.T) {
361361
t.Errorf("error from Stat on closed file did not match ErrClosed: %q, type %T", err, err)
362362
}
363363
}
364+
365+
func TestStatNotExist(t *testing.T) {
366+
t.Parallel()
367+
name := filepath.Join(t.TempDir(), "notfound")
368+
_, err := os.Stat(name)
369+
if !errors.Is(err, fs.ErrNotExist) {
370+
t.Errorf("os.Stat(%q) = %v; want fs.ErrNotExist", name, err)
371+
}
372+
373+
name = filepath.Join(t.TempDir(), "notfounddir", "notfound")
374+
_, err = os.Stat(name)
375+
if !errors.Is(err, fs.ErrNotExist) {
376+
t.Errorf("os.Stat(%q) = %v; want fs.ErrNotExist", name, err)
377+
}
378+
}

src/os/stat_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package os
66

77
import (
8+
"errors"
89
"internal/filepathlite"
910
"internal/syscall/windows"
1011
"syscall"
@@ -34,6 +35,9 @@ func stat(funcname, name string, followSurrogates bool) (FileInfo, error) {
3435
// See https://golang.org/issues/19922#issuecomment-300031421 for details.
3536
var fa syscall.Win32FileAttributeData
3637
err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
38+
if errors.Is(err, ErrNotExist) {
39+
return nil, &PathError{Op: "GetFileAttributesEx", Path: name, Err: err}
40+
}
3741
if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
3842
// Not a surrogate for another named entity, because it isn't any kind of reparse point.
3943
// The information we got from GetFileAttributesEx is good enough for now.

0 commit comments

Comments
 (0)