}
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()
if errno != nil {
return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno}
}
- f.dirinfo = &dirInfo{
- dir: dir,
+ d = &dirInfo{dir: dir}
+ if f.dirinfo.CompareAndSwap(nil, d) {
+ break
}
+ // We lost the race: try again.
+ d.close()
}
- d := f.dirinfo
size := n
if size <= 0 {
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 file.dirinfo == nil {
- file.dirinfo = new(dirInfo)
+ d := file.dirinfo.Load()
+ if d == nil {
+ d = new(dirInfo)
+ file.dirinfo.Store(d)
}
- d := file.dirinfo
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
size := n
if size <= 0 {
size = 100
// Auxiliary information if the File describes a directory
type dirInfo struct {
+ mu sync.Mutex
buf *[]byte // buffer for directory I/O
nbuf int // length of buf; return value from Getdirentries
bufp int // location of next record in buf.
}
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 f.dirinfo == nil {
- f.dirinfo = new(dirInfo)
+ // If this file has no dirInfo, create one.
+ d := f.dirinfo.Load()
+ if d == nil {
+ d = new(dirInfo)
+ f.dirinfo.Store(d)
}
- d := f.dirinfo
+ d.mu.Lock()
+ defer d.mu.Unlock()
if d.buf == nil {
- f.dirinfo.buf = dirBufPool.Get().(*[]byte)
+ d.buf = dirBufPool.Get().(*[]byte)
}
// Change the meaning of n for the implementation below.
// Auxiliary information if the File describes a directory
type dirInfo struct {
+ mu sync.Mutex
// buf is a slice pointer so the slice header
// does not escape to the heap when returning
// buf to dirBufPool.
}
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
- if file.dirinfo == nil {
- file.dirinfo = new(dirInfo)
- file.dirinfo.init(file.pfd.Sysfd)
+ // If this file has no dirInfo, create one.
+ var d *dirInfo
+ for {
+ d = file.dirinfo.Load()
+ if d != nil {
+ break
+ }
+ d = new(dirInfo)
+ d.init(file.pfd.Sysfd)
+ if file.dirinfo.CompareAndSwap(nil, d) {
+ break
+ }
+ // We lost the race: try again.
+ d.close()
}
- d := file.dirinfo
+ d.mu.Lock()
+ defer d.mu.Unlock()
if d.buf == nil {
d.buf = dirBufPool.Get().(*[]byte)
}
+
wantAll := n <= 0
if wantAll {
n = -1
// }
// fmt.Printf("read %d bytes: %q\n", count, data[:count])
//
-// Note: 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.
+// # Concurrency
+//
+// 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
import (
)
// 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 }
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
return 0, err
}
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
}
if e != nil {
"internal/poll"
"io"
"runtime"
+ "sync"
+ "sync/atomic"
"syscall"
"time"
)
fdmu poll.FDMutex
fd int
name string
- dirinfo *dirInfo // nil unless directory being read
- appendMode bool // whether file is opened for appending
+ dirinfo atomic.Pointer[dirInfo] // nil unless directory being read
+ appendMode bool // whether file is opened for appending
}
// Fd returns the integer Plan 9 file descriptor referencing the open file.
// Auxiliary information if the File describes a directory
type dirInfo struct {
+ mu sync.Mutex
buf [syscall.STATMAX]byte // buffer for directory I/O
nbuf int // length of buf; return value from Read
bufp int // location of next record in buf.
return 0, err
}
defer f.decref()
- if f.dirinfo != nil {
- // Free cached dirinfo, so we allocate a new one if we
- // access this file as a directory again. See #35767 and #37161.
- f.dirinfo = nil
- }
+ // Free cached dirinfo, so we allocate a new one if we
+ // access this file as a directory again. See #35767 and #37161.
+ f.dirinfo.Store(nil)
return syscall.Seek(f.fd, offset, whence)
}
"internal/syscall/unix"
"io/fs"
"runtime"
+ "sync/atomic"
"syscall"
_ "unsafe" // for go:linkname
)
type file struct {
pfd poll.FD
name string
- dirinfo *dirInfo // nil unless directory being read
- nonblock bool // whether we set nonblocking mode
- stdoutOrErr bool // whether this is stdout or stderr
- appendMode bool // whether file is opened for appending
+ dirinfo atomic.Pointer[dirInfo] // nil unless directory being read
+ nonblock bool // whether we set nonblocking mode
+ stdoutOrErr bool // whether this is stdout or stderr
+ appendMode bool // whether file is opened for appending
}
// Fd returns the integer Unix file descriptor referencing the open file.
if file == nil {
return syscall.EINVAL
}
- if file.dirinfo != nil {
- file.dirinfo.close()
- file.dirinfo = nil
+ if info := file.dirinfo.Swap(nil); info != nil {
+ info.close()
}
var err error
if e := file.pfd.Close(); e != nil {
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
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
// access this file as a directory again. See #35767 and #37161.
- f.dirinfo.close()
- f.dirinfo = nil
+ info.close()
}
ret, err = f.pfd.Seek(offset, whence)
runtime.KeepAlive(f)
"internal/syscall/windows"
"runtime"
"sync"
+ "sync/atomic"
"syscall"
"unsafe"
)
type file struct {
pfd poll.FD
name string
- dirinfo *dirInfo // nil unless directory being read
- appendMode bool // whether file is opened for appending
+ dirinfo atomic.Pointer[dirInfo] // nil unless directory being read
+ appendMode bool // whether file is opened for appending
}
// Fd returns the Windows handle referencing the open file.
if file == nil {
return syscall.EINVAL
}
- if file.dirinfo != nil {
- file.dirinfo.close()
- file.dirinfo = nil
+ if info := file.dirinfo.Swap(nil); info != nil {
+ info.close()
}
var err error
if e := file.pfd.Close(); e != nil {
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
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
// access this file as a directory again. See #35767 and #37161.
- f.dirinfo.close()
- f.dirinfo = nil
+ info.close()
}
ret, err = f.pfd.Seek(offset, whence)
runtime.KeepAlive(f)
func Getpagesize() int { return syscall.Getpagesize() }
// File represents an open file descriptor.
+//
+// The methods of File are safe for concurrent use.
type File struct {
*file // os specific
}