Skip to content

Commit c05b06a

Browse files
os: use poller for file I/O
This changes the os package to use the runtime poller for file I/O where possible. When a system call blocks on a pollable descriptor, the goroutine will be blocked on the poller but the thread will be released to run other goroutines. When using a non-pollable descriptor, the os package will continue to use thread-blocking system calls as before. For example, on GNU/Linux, the runtime poller uses epoll. epoll does not support ordinary disk files, so they will continue to use blocking I/O as before. The poller will be used for pipes. Since this means that the poller is used for many more programs, this modifies the runtime to only block waiting for the poller if there is some goroutine that is waiting on the poller. Otherwise, there is no point, as the poller will never make any goroutine ready. This preserves the runtime's current simple deadlock detection. This seems to crash FreeBSD systems, so it is disabled on FreeBSD. This is issue 19093. Using the poller on Windows requires opening the file with FILE_FLAG_OVERLAPPED. We should only do that if we can remove that flag if the program calls the Fd method. This is issue 19098. Update #6817. Update #7903. Update #15021. Update #18507. Update #19093. Update #19098. Change-Id: Ia5197dcefa7c6fbcca97d19a6f8621b2abcbb1fe Reviewed-on: https://go-review.googlesource.com/36800 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 81ec3f6 commit c05b06a

29 files changed

+408
-337
lines changed

src/internal/poll/fd_poll_nacl.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,7 @@ func setDeadlineImpl(fd *FD, t time.Time, mode int) error {
8585
fd.decref()
8686
return nil
8787
}
88+
89+
func PollDescriptor() uintptr {
90+
return ^uintptr(0)
91+
}

src/internal/poll/fd_poll_runtime.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
func runtimeNano() int64
1818

1919
func runtime_pollServerInit()
20+
func runtime_pollServerDescriptor() uintptr
2021
func runtime_pollOpen(fd uintptr) (uintptr, int)
2122
func runtime_pollClose(ctx uintptr)
2223
func runtime_pollWait(ctx uintptr, mode int) int
@@ -146,3 +147,9 @@ func setDeadlineImpl(fd *FD, t time.Time, mode int) error {
146147
fd.decref()
147148
return nil
148149
}
150+
151+
// PollDescriptor returns the descriptor being used by the poller,
152+
// or ^uintptr(0) if there isn't one. This is only used for testing.
153+
func PollDescriptor() uintptr {
154+
return runtime_pollServerDescriptor()
155+
}

src/internal/poll/fd_unix.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,19 @@ func (fd *FD) ReadDirent(buf []byte) (int, error) {
365365
return 0, err
366366
}
367367
defer fd.decref()
368-
return syscall.ReadDirent(fd.Sysfd, buf)
368+
for {
369+
n, err := syscall.ReadDirent(fd.Sysfd, buf)
370+
if err != nil {
371+
n = 0
372+
if err == syscall.EAGAIN {
373+
if err = fd.pd.waitRead(); err == nil {
374+
continue
375+
}
376+
}
377+
}
378+
// Do not call eofError; caller does not expect to see io.EOF.
379+
return n, err
380+
}
369381
}
370382

371383
// Fchdir wraps syscall.Fchdir.

src/internal/poll/fd_windows.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,13 +523,15 @@ func (fd *FD) Pread(b []byte, off int64) (int, error) {
523523
var done uint32
524524
e = syscall.ReadFile(fd.Sysfd, b, &done, &o)
525525
if e != nil {
526+
done = 0
526527
if e == syscall.ERROR_HANDLE_EOF {
527-
// end of file
528-
return 0, nil
528+
e = io.EOF
529529
}
530-
return 0, e
531530
}
532-
return int(done), nil
531+
if len(b) != 0 {
532+
e = fd.eofError(int(done), e)
533+
}
534+
return int(done), e
533535
}
534536

535537
func (fd *FD) RecvFrom(buf []byte) (int, syscall.Sockaddr, error) {

src/os/dir_unix.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package os
88

99
import (
1010
"io"
11+
"runtime"
1112
"syscall"
1213
)
1314

@@ -63,9 +64,10 @@ func (f *File) readdirnames(n int) (names []string, err error) {
6364
if d.bufp >= d.nbuf {
6465
d.bufp = 0
6566
var errno error
66-
d.nbuf, errno = fixCount(syscall.ReadDirent(f.fd, d.buf))
67+
d.nbuf, errno = f.pfd.ReadDirent(d.buf)
68+
runtime.KeepAlive(f)
6769
if errno != nil {
68-
return names, NewSyscallError("readdirent", errno)
70+
return names, wrapSyscallError("readdirent", errno)
6971
}
7072
if d.nbuf <= 0 {
7173
break // EOF

src/os/dir_windows.go

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

77
import (
88
"io"
9+
"runtime"
910
"syscall"
1011
)
1112

@@ -16,7 +17,7 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) {
1617
if !file.isdir() {
1718
return nil, &PathError{"Readdir", file.name, syscall.ENOTDIR}
1819
}
19-
if !file.dirinfo.isempty && file.fd == syscall.InvalidHandle {
20+
if !file.dirinfo.isempty && file.pfd.Sysfd == syscall.InvalidHandle {
2021
return nil, syscall.EINVAL
2122
}
2223
wantAll := n <= 0
@@ -29,7 +30,8 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) {
2930
d := &file.dirinfo.data
3031
for n != 0 && !file.dirinfo.isempty {
3132
if file.dirinfo.needdata {
32-
e := syscall.FindNextFile(file.fd, d)
33+
e := file.pfd.FindNextFile(d)
34+
runtime.KeepAlive(file)
3335
if e != nil {
3436
if e == syscall.ERROR_NO_MORE_FILES {
3537
break

src/os/error_posix.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2017 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+
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
6+
7+
package os
8+
9+
import "syscall"
10+
11+
// wrapSyscallError takes an error and a syscall name. If the error is
12+
// a syscall.Errno, it wraps it in a os.SyscallError using the syscall name.
13+
func wrapSyscallError(name string, err error) error {
14+
if _, ok := err.(syscall.Errno); ok {
15+
err = NewSyscallError(name, err)
16+
}
17+
return err
18+
}

src/os/exec/exec_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"bytes"
1313
"context"
1414
"fmt"
15+
"internal/poll"
1516
"internal/testenv"
1617
"io"
1718
"io/ioutil"
@@ -369,12 +370,16 @@ var testedAlreadyLeaked = false
369370

370371
// basefds returns the number of expected file descriptors
371372
// to be present in a process at start.
373+
// stdin, stdout, stderr, epoll/kqueue
372374
func basefds() uintptr {
373375
return os.Stderr.Fd() + 1
374376
}
375377

376378
func closeUnexpectedFds(t *testing.T, m string) {
377379
for fd := basefds(); fd <= 101; fd++ {
380+
if fd == poll.PollDescriptor() {
381+
continue
382+
}
378383
err := os.NewFile(fd, "").Close()
379384
if err == nil {
380385
t.Logf("%s: Something already leaked - closed fd %d", m, fd)
@@ -732,6 +737,9 @@ func TestHelperProcess(*testing.T) {
732737
// Now verify that there are no other open fds.
733738
var files []*os.File
734739
for wantfd := basefds() + 1; wantfd <= 100; wantfd++ {
740+
if wantfd == poll.PollDescriptor() {
741+
continue
742+
}
735743
f, err := os.Open(os.Args[0])
736744
if err != nil {
737745
fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)

src/os/export_windows_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package os
77
// Export for testing.
88

99
var (
10-
FixLongPath = fixLongPath
11-
NewConsoleFile = newConsoleFile
12-
ReadConsoleFunc = &readConsole
10+
FixLongPath = fixLongPath
11+
NewConsoleFile = newConsoleFile
1312
)

src/os/file.go

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,12 @@ func (f *File) Read(b []byte) (n int, err error) {
9999
return 0, err
100100
}
101101
n, e := f.read(b)
102-
if n == 0 && len(b) > 0 && e == nil {
103-
return 0, io.EOF
104-
}
105102
if e != nil {
106-
err = &PathError{"read", f.name, e}
103+
if e == io.EOF {
104+
err = e
105+
} else {
106+
err = &PathError{"read", f.name, e}
107+
}
107108
}
108109
return n, err
109110
}
@@ -118,11 +119,12 @@ func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
118119
}
119120
for len(b) > 0 {
120121
m, e := f.pread(b, off)
121-
if m == 0 && e == nil {
122-
return n, io.EOF
123-
}
124122
if e != nil {
125-
err = &PathError{"read", f.name, e}
123+
if e == io.EOF {
124+
err = e
125+
} else {
126+
err = &PathError{"read", f.name, e}
127+
}
126128
break
127129
}
128130
n += m
@@ -226,19 +228,6 @@ func Chdir(dir string) error {
226228
return nil
227229
}
228230

229-
// Chdir changes the current working directory to the file,
230-
// which must be a directory.
231-
// If there is an error, it will be of type *PathError.
232-
func (f *File) Chdir() error {
233-
if err := f.checkValid("chdir"); err != nil {
234-
return err
235-
}
236-
if e := syscall.Fchdir(f.fd); e != nil {
237-
return &PathError{"chdir", f.name, e}
238-
}
239-
return nil
240-
}
241-
242231
// Open opens the named file for reading. If successful, methods on
243232
// the returned file can be used for reading; the associated file
244233
// descriptor has mode O_RDONLY.
@@ -275,15 +264,3 @@ func fixCount(n int, err error) (int, error) {
275264
}
276265
return n, err
277266
}
278-
279-
// checkValid checks whether f is valid for use.
280-
// If not, it returns an appropriate error, perhaps incorporating the operation name op.
281-
func (f *File) checkValid(op string) error {
282-
if f == nil {
283-
return ErrInvalid
284-
}
285-
if f.fd == badFd {
286-
return &PathError{op, f.name, ErrClosed}
287-
}
288-
return nil
289-
}

src/os/file_plan9.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,22 @@ func (f *File) Sync() error {
244244
// read reads up to len(b) bytes from the File.
245245
// It returns the number of bytes read and an error, if any.
246246
func (f *File) read(b []byte) (n int, err error) {
247-
return fixCount(syscall.Read(f.fd, b))
247+
n, e := fixCount(syscall.Read(f.fd, b))
248+
if n == 0 && len(b) > 0 && e == nil {
249+
return 0, io.EOF
250+
}
251+
return n, e
248252
}
249253

250254
// pread reads len(b) bytes from the File starting at byte offset off.
251255
// It returns the number of bytes read and the error, if any.
252256
// EOF is signaled by a zero count with err set to nil.
253257
func (f *File) pread(b []byte, off int64) (n int, err error) {
254-
return fixCount(syscall.Pread(f.fd, b, off))
258+
n, e := fixCount(syscall.Pread(f.fd, b, off))
259+
if n == 0 && len(b) > 0 && e == nil {
260+
return 0, io.EOF
261+
}
262+
return n, e
255263
}
256264

257265
// write writes len(b) bytes to the File.
@@ -472,3 +480,28 @@ func (f *File) Chown(uid, gid int) error {
472480
func TempDir() string {
473481
return "/tmp"
474482
}
483+
484+
// Chdir changes the current working directory to the file,
485+
// which must be a directory.
486+
// If there is an error, it will be of type *PathError.
487+
func (f *File) Chdir() error {
488+
if err := f.checkValid("chdir"); err != nil {
489+
return err
490+
}
491+
if e := syscall.Fchdir(f.fd); e != nil {
492+
return &PathError{"chdir", f.name, e}
493+
}
494+
return nil
495+
}
496+
497+
// checkValid checks whether f is valid for use.
498+
// If not, it returns an appropriate error, perhaps incorporating the operation name op.
499+
func (f *File) checkValid(op string) error {
500+
if f == nil {
501+
return ErrInvalid
502+
}
503+
if f.fd == badFd {
504+
return &PathError{op, f.name, ErrClosed}
505+
}
506+
return nil
507+
}

src/os/file_posix.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package os
88

99
import (
10+
"runtime"
1011
"syscall"
1112
"time"
1213
)
@@ -60,9 +61,10 @@ func (f *File) Chmod(mode FileMode) error {
6061
if err := f.checkValid("chmod"); err != nil {
6162
return err
6263
}
63-
if e := syscall.Fchmod(f.fd, syscallMode(mode)); e != nil {
64+
if e := f.pfd.Fchmod(syscallMode(mode)); e != nil {
6465
return &PathError{"chmod", f.name, e}
6566
}
67+
runtime.KeepAlive(f)
6668
return nil
6769
}
6870

@@ -92,9 +94,10 @@ func (f *File) Chown(uid, gid int) error {
9294
if err := f.checkValid("chown"); err != nil {
9395
return err
9496
}
95-
if e := syscall.Fchown(f.fd, uid, gid); e != nil {
97+
if e := f.pfd.Fchown(uid, gid); e != nil {
9698
return &PathError{"chown", f.name, e}
9799
}
100+
runtime.KeepAlive(f)
98101
return nil
99102
}
100103

@@ -105,9 +108,10 @@ func (f *File) Truncate(size int64) error {
105108
if err := f.checkValid("truncate"); err != nil {
106109
return err
107110
}
108-
if e := syscall.Ftruncate(f.fd, size); e != nil {
111+
if e := f.pfd.Ftruncate(size); e != nil {
109112
return &PathError{"truncate", f.name, e}
110113
}
114+
runtime.KeepAlive(f)
111115
return nil
112116
}
113117

@@ -118,9 +122,10 @@ func (f *File) Sync() error {
118122
if err := f.checkValid("sync"); err != nil {
119123
return err
120124
}
121-
if e := syscall.Fsync(f.fd); e != nil {
125+
if e := f.pfd.Fsync(); e != nil {
122126
return &PathError{"sync", f.name, e}
123127
}
128+
runtime.KeepAlive(f)
124129
return nil
125130
}
126131

@@ -139,3 +144,29 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error {
139144
}
140145
return nil
141146
}
147+
148+
// Chdir changes the current working directory to the file,
149+
// which must be a directory.
150+
// If there is an error, it will be of type *PathError.
151+
func (f *File) Chdir() error {
152+
if err := f.checkValid("chdir"); err != nil {
153+
return err
154+
}
155+
if e := f.pfd.Fchdir(); e != nil {
156+
return &PathError{"chdir", f.name, e}
157+
}
158+
runtime.KeepAlive(f)
159+
return nil
160+
}
161+
162+
// checkValid checks whether f is valid for use.
163+
// If not, it returns an appropriate error, perhaps incorporating the operation name op.
164+
func (f *File) checkValid(op string) error {
165+
if f == nil {
166+
return ErrInvalid
167+
}
168+
if f.pfd.Sysfd == badFd {
169+
return &PathError{op, f.name, ErrClosed}
170+
}
171+
return nil
172+
}

0 commit comments

Comments
 (0)