os: don't try to make the directory FD non-blocking in os.ReadDir

This will fail because epoll_ctl() fails on directory FDs, so we
end up issuing unnecessary syscalls. My test program that calls
filepath.WalkDir on a large directory tree runs 1.23 ± 0.04 times
faster than with the original implementation.

Change-Id: Ie33d798c48057a7b2d0bacac80fcdde5b5a8bb1b
Reviewed-on: https://go-review.googlesource.com/c/go/+/570877
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
This commit is contained in:
Peter Collingbourne 2024-03-11 20:10:48 -07:00 committed by Gopher Robot
parent bedda24574
commit b822f098c5
5 changed files with 45 additions and 3 deletions

View File

@ -115,7 +115,7 @@ var testingForceReadDirLstat bool
// ReadDir returns the entries it was able to read before the error,
// along with the error.
func ReadDir(name string) ([]DirEntry, error) {
f, err := Open(name)
f, err := openDir(name)
if err != nil {
return nil, err
}

View File

@ -380,6 +380,14 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) {
return f, nil
}
// openDir opens a file which is assumed to be a directory. As such, it skips
// the syscalls that make the file descriptor non-blocking as these take time
// and will fail on file descriptors for directories.
func openDir(name string) (*File, error) {
testlog.Open(name)
return openDirNolog(name)
}
// lstat is overridden in tests.
var lstat = Lstat

View File

@ -139,6 +139,10 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
return NewFile(uintptr(fd), name), nil
}
func openDirNolog(name string) (*File, error) {
return openFileNolog(name, O_RDONLY, 0)
}
// Close closes the File, rendering it unusable for I/O.
// On files that support SetDeadline, any pending I/O operations will
// be canceled and return immediately with an ErrClosed error.

View File

@ -157,7 +157,7 @@ const (
kindNonBlock
// kindNoPoll means that we should not put the descriptor into
// non-blocking mode, because we know it is not a pipe or FIFO.
// Used by openFdAt for directories.
// Used by openFdAt and openDirNolog for directories.
kindNoPoll
)
@ -256,7 +256,7 @@ func epipecheck(file *File, e error) {
const DevNull = "/dev/null"
// openFileNolog is the Unix implementation of OpenFile.
// Changes here should be reflected in openFdAt, if relevant.
// Changes here should be reflected in openFdAt and openDirNolog, if relevant.
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
setSticky := false
if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
@ -303,6 +303,32 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
return f, nil
}
func openDirNolog(name string) (*File, error) {
var r int
var s poll.SysFile
for {
var e error
r, s, e = open(name, O_RDONLY|syscall.O_CLOEXEC, 0)
if e == nil {
break
}
if e == syscall.EINTR {
continue
}
return nil, &PathError{Op: "open", Path: name, Err: e}
}
if !supportsCloseOnExec {
syscall.CloseOnExec(r)
}
f := newFile(r, name, kindNoPoll)
f.pfd.SysFile = s
return f, nil
}
func (file *file) close() error {
if file == nil {
return syscall.EINVAL

View File

@ -119,6 +119,10 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
return newFile(r, name, "file"), nil
}
func openDirNolog(name string) (*File, error) {
return openFileNolog(name, O_RDONLY, 0)
}
func (file *file) close() error {
if file == nil {
return syscall.EINVAL