mirror of
https://github.com/golang/go.git
synced 2025-05-17 13:24:38 +00:00
os: make File.Readdir et al concurrency-safe
Before, all methods of File (including Close) were safe for concurrent use (I checked), except the three variants of ReadDir. This change makes the ReadDir operations atomic too, and documents explicitly that all methods of File have this property, which was already implied by the package documentation. Fixes #66498 Change-Id: I05c88b4e60b44c702062e99ed8f4a32e7945927a Reviewed-on: https://go-review.googlesource.com/c/go/+/578322 Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
7418d419af
commit
ca94e9e223
@ -25,16 +25,24 @@ func (d *dirInfo) close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
||||||
if f.dirinfo == nil {
|
// If this file has no dirinfo, create one.
|
||||||
|
var d *dirInfo
|
||||||
|
for {
|
||||||
|
d = f.dirinfo.Load()
|
||||||
|
if d != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
dir, call, errno := f.pfd.OpenDir()
|
dir, call, errno := f.pfd.OpenDir()
|
||||||
if errno != nil {
|
if errno != nil {
|
||||||
return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno}
|
return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno}
|
||||||
}
|
}
|
||||||
f.dirinfo = &dirInfo{
|
d = &dirInfo{dir: dir}
|
||||||
dir: dir,
|
if f.dirinfo.CompareAndSwap(nil, d) {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
// We lost the race: try again.
|
||||||
|
d.close()
|
||||||
}
|
}
|
||||||
d := f.dirinfo
|
|
||||||
|
|
||||||
size := n
|
size := n
|
||||||
if size <= 0 {
|
if size <= 0 {
|
||||||
|
@ -12,10 +12,14 @@ import (
|
|||||||
|
|
||||||
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
||||||
// If this file has no dirinfo, create one.
|
// If this file has no dirinfo, create one.
|
||||||
if file.dirinfo == nil {
|
d := file.dirinfo.Load()
|
||||||
file.dirinfo = new(dirInfo)
|
if d == nil {
|
||||||
|
d = new(dirInfo)
|
||||||
|
file.dirinfo.Store(d)
|
||||||
}
|
}
|
||||||
d := file.dirinfo
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
size := n
|
size := n
|
||||||
if size <= 0 {
|
if size <= 0 {
|
||||||
size = 100
|
size = 100
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
// Auxiliary information if the File describes a directory
|
// Auxiliary information if the File describes a directory
|
||||||
type dirInfo struct {
|
type dirInfo struct {
|
||||||
|
mu sync.Mutex
|
||||||
buf *[]byte // buffer for directory I/O
|
buf *[]byte // buffer for directory I/O
|
||||||
nbuf int // length of buf; return value from Getdirentries
|
nbuf int // length of buf; return value from Getdirentries
|
||||||
bufp int // location of next record in buf.
|
bufp int // location of next record in buf.
|
||||||
@ -43,13 +44,16 @@ func (d *dirInfo) close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
||||||
// If this file has no dirinfo, create one.
|
// If this file has no dirInfo, create one.
|
||||||
if f.dirinfo == nil {
|
d := f.dirinfo.Load()
|
||||||
f.dirinfo = new(dirInfo)
|
if d == nil {
|
||||||
|
d = new(dirInfo)
|
||||||
|
f.dirinfo.Store(d)
|
||||||
}
|
}
|
||||||
d := f.dirinfo
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
if d.buf == nil {
|
if d.buf == nil {
|
||||||
f.dirinfo.buf = dirBufPool.Get().(*[]byte)
|
d.buf = dirBufPool.Get().(*[]byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the meaning of n for the implementation below.
|
// Change the meaning of n for the implementation below.
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
// Auxiliary information if the File describes a directory
|
// Auxiliary information if the File describes a directory
|
||||||
type dirInfo struct {
|
type dirInfo struct {
|
||||||
|
mu sync.Mutex
|
||||||
// buf is a slice pointer so the slice header
|
// buf is a slice pointer so the slice header
|
||||||
// does not escape to the heap when returning
|
// does not escape to the heap when returning
|
||||||
// buf to dirBufPool.
|
// buf to dirBufPool.
|
||||||
@ -93,14 +94,27 @@ func (d *dirInfo) init(h syscall.Handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
||||||
if file.dirinfo == nil {
|
// If this file has no dirInfo, create one.
|
||||||
file.dirinfo = new(dirInfo)
|
var d *dirInfo
|
||||||
file.dirinfo.init(file.pfd.Sysfd)
|
for {
|
||||||
|
d = file.dirinfo.Load()
|
||||||
|
if d != nil {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
d := file.dirinfo
|
d = new(dirInfo)
|
||||||
|
d.init(file.pfd.Sysfd)
|
||||||
|
if file.dirinfo.CompareAndSwap(nil, d) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// We lost the race: try again.
|
||||||
|
d.close()
|
||||||
|
}
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
if d.buf == nil {
|
if d.buf == nil {
|
||||||
d.buf = dirBufPool.Get().(*[]byte)
|
d.buf = dirBufPool.Get().(*[]byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantAll := n <= 0
|
wantAll := n <= 0
|
||||||
if wantAll {
|
if wantAll {
|
||||||
n = -1
|
n = -1
|
||||||
|
@ -34,9 +34,13 @@
|
|||||||
// }
|
// }
|
||||||
// fmt.Printf("read %d bytes: %q\n", count, data[:count])
|
// fmt.Printf("read %d bytes: %q\n", count, data[:count])
|
||||||
//
|
//
|
||||||
// Note: The maximum number of concurrent operations on a File may be limited by
|
// # Concurrency
|
||||||
// the OS or the system. The number should be high, but exceeding it may degrade
|
//
|
||||||
// performance or cause other issues.
|
// The methods of [File] correspond to file system operations. All are
|
||||||
|
// safe for concurrent use. The maximum number of concurrent
|
||||||
|
// operations on a File may be limited by the OS or the system. The
|
||||||
|
// number should be high, but exceeding it may degrade performance or
|
||||||
|
// cause other issues.
|
||||||
package os
|
package os
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -53,6 +57,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Name returns the name of the file as presented to Open.
|
// Name returns the name of the file as presented to Open.
|
||||||
|
//
|
||||||
|
// It is safe to call Name after [Close].
|
||||||
func (f *File) Name() string { return f.name }
|
func (f *File) Name() string { return f.name }
|
||||||
|
|
||||||
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
|
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
|
||||||
@ -279,7 +285,7 @@ func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
r, e := f.seek(offset, whence)
|
r, e := f.seek(offset, whence)
|
||||||
if e == nil && f.dirinfo != nil && r != 0 {
|
if e == nil && f.dirinfo.Load() != nil && r != 0 {
|
||||||
e = syscall.EISDIR
|
e = syscall.EISDIR
|
||||||
}
|
}
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"internal/poll"
|
"internal/poll"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -26,7 +28,7 @@ type file struct {
|
|||||||
fdmu poll.FDMutex
|
fdmu poll.FDMutex
|
||||||
fd int
|
fd int
|
||||||
name string
|
name string
|
||||||
dirinfo *dirInfo // nil unless directory being read
|
dirinfo atomic.Pointer[dirInfo] // nil unless directory being read
|
||||||
appendMode bool // whether file is opened for appending
|
appendMode bool // whether file is opened for appending
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +62,7 @@ func NewFile(fd uintptr, name string) *File {
|
|||||||
|
|
||||||
// Auxiliary information if the File describes a directory
|
// Auxiliary information if the File describes a directory
|
||||||
type dirInfo struct {
|
type dirInfo struct {
|
||||||
|
mu sync.Mutex
|
||||||
buf [syscall.STATMAX]byte // buffer for directory I/O
|
buf [syscall.STATMAX]byte // buffer for directory I/O
|
||||||
nbuf int // length of buf; return value from Read
|
nbuf int // length of buf; return value from Read
|
||||||
bufp int // location of next record in buf.
|
bufp int // location of next record in buf.
|
||||||
@ -349,11 +352,9 @@ func (f *File) seek(offset int64, whence int) (ret int64, err error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer f.decref()
|
defer f.decref()
|
||||||
if f.dirinfo != nil {
|
|
||||||
// Free cached dirinfo, so we allocate a new one if we
|
// Free cached dirinfo, so we allocate a new one if we
|
||||||
// access this file as a directory again. See #35767 and #37161.
|
// access this file as a directory again. See #35767 and #37161.
|
||||||
f.dirinfo = nil
|
f.dirinfo.Store(nil)
|
||||||
}
|
|
||||||
return syscall.Seek(f.fd, offset, whence)
|
return syscall.Seek(f.fd, offset, whence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"internal/syscall/unix"
|
"internal/syscall/unix"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
_ "unsafe" // for go:linkname
|
_ "unsafe" // for go:linkname
|
||||||
)
|
)
|
||||||
@ -58,7 +59,7 @@ func rename(oldname, newname string) error {
|
|||||||
type file struct {
|
type file struct {
|
||||||
pfd poll.FD
|
pfd poll.FD
|
||||||
name string
|
name string
|
||||||
dirinfo *dirInfo // nil unless directory being read
|
dirinfo atomic.Pointer[dirInfo] // nil unless directory being read
|
||||||
nonblock bool // whether we set nonblocking mode
|
nonblock bool // whether we set nonblocking mode
|
||||||
stdoutOrErr bool // whether this is stdout or stderr
|
stdoutOrErr bool // whether this is stdout or stderr
|
||||||
appendMode bool // whether file is opened for appending
|
appendMode bool // whether file is opened for appending
|
||||||
@ -325,9 +326,8 @@ func (file *file) close() error {
|
|||||||
if file == nil {
|
if file == nil {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
if file.dirinfo != nil {
|
if info := file.dirinfo.Swap(nil); info != nil {
|
||||||
file.dirinfo.close()
|
info.close()
|
||||||
file.dirinfo = nil
|
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if e := file.pfd.Close(); e != nil {
|
if e := file.pfd.Close(); e != nil {
|
||||||
@ -347,11 +347,10 @@ func (file *file) close() error {
|
|||||||
// relative to the current offset, and 2 means relative to the end.
|
// relative to the current offset, and 2 means relative to the end.
|
||||||
// It returns the new offset and an error, if any.
|
// It returns the new offset and an error, if any.
|
||||||
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
|
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
|
||||||
if f.dirinfo != nil {
|
if info := f.dirinfo.Swap(nil); info != nil {
|
||||||
// Free cached dirinfo, so we allocate a new one if we
|
// Free cached dirinfo, so we allocate a new one if we
|
||||||
// access this file as a directory again. See #35767 and #37161.
|
// access this file as a directory again. See #35767 and #37161.
|
||||||
f.dirinfo.close()
|
info.close()
|
||||||
f.dirinfo = nil
|
|
||||||
}
|
}
|
||||||
ret, err = f.pfd.Seek(offset, whence)
|
ret, err = f.pfd.Seek(offset, whence)
|
||||||
runtime.KeepAlive(f)
|
runtime.KeepAlive(f)
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"internal/syscall/windows"
|
"internal/syscall/windows"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@ -25,7 +26,7 @@ const _UTIME_OMIT = -1
|
|||||||
type file struct {
|
type file struct {
|
||||||
pfd poll.FD
|
pfd poll.FD
|
||||||
name string
|
name string
|
||||||
dirinfo *dirInfo // nil unless directory being read
|
dirinfo atomic.Pointer[dirInfo] // nil unless directory being read
|
||||||
appendMode bool // whether file is opened for appending
|
appendMode bool // whether file is opened for appending
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,9 +128,8 @@ func (file *file) close() error {
|
|||||||
if file == nil {
|
if file == nil {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
if file.dirinfo != nil {
|
if info := file.dirinfo.Swap(nil); info != nil {
|
||||||
file.dirinfo.close()
|
info.close()
|
||||||
file.dirinfo = nil
|
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if e := file.pfd.Close(); e != nil {
|
if e := file.pfd.Close(); e != nil {
|
||||||
@ -149,11 +149,10 @@ func (file *file) close() error {
|
|||||||
// relative to the current offset, and 2 means relative to the end.
|
// relative to the current offset, and 2 means relative to the end.
|
||||||
// It returns the new offset and an error, if any.
|
// It returns the new offset and an error, if any.
|
||||||
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
|
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
|
||||||
if f.dirinfo != nil {
|
if info := f.dirinfo.Swap(nil); info != nil {
|
||||||
// Free cached dirinfo, so we allocate a new one if we
|
// Free cached dirinfo, so we allocate a new one if we
|
||||||
// access this file as a directory again. See #35767 and #37161.
|
// access this file as a directory again. See #35767 and #37161.
|
||||||
f.dirinfo.close()
|
info.close()
|
||||||
f.dirinfo = nil
|
|
||||||
}
|
}
|
||||||
ret, err = f.pfd.Seek(offset, whence)
|
ret, err = f.pfd.Seek(offset, whence)
|
||||||
runtime.KeepAlive(f)
|
runtime.KeepAlive(f)
|
||||||
|
@ -13,6 +13,8 @@ import (
|
|||||||
func Getpagesize() int { return syscall.Getpagesize() }
|
func Getpagesize() int { return syscall.Getpagesize() }
|
||||||
|
|
||||||
// File represents an open file descriptor.
|
// File represents an open file descriptor.
|
||||||
|
//
|
||||||
|
// The methods of File are safe for concurrent use.
|
||||||
type File struct {
|
type File struct {
|
||||||
*file // os specific
|
*file // os specific
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user