Skip to content

Commit 2e29ae0

Browse files
ianlancetaylorromaindoumenc
authored andcommitted
os: use poll.fdMutex for Plan 9 files
This permits us to safely support concurrent access to files on Plan 9. Concurrent access was already safe on other systems. This does introduce a change: if one goroutine calls a blocking read on a pipe, and another goroutine closes the pipe, then before this CL the close would occur. Now the close will be delayed until the blocking read completes. Also add tests that concurrent I/O and Close on a pipe are OK. For golang#50436 For golang#56043 Change-Id: I969c869ea3b8c5c2f2ef319e441a56a3c64e7bf5 Reviewed-on: https://go-review.googlesource.com/c/go/+/438347 Reviewed-by: Bryan Mills <bcmills@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Reviewed-by: David du Colombier <0intro@gmail.com> Run-TryBot: Ian Lance Taylor <iant@google.com> Auto-Submit: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
1 parent 85650a6 commit 2e29ae0

File tree

8 files changed

+306
-28
lines changed

8 files changed

+306
-28
lines changed

Diff for: src/internal/poll/export_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,26 @@ package poll
1010

1111
var Consume = consume
1212

13-
type FDMutex struct {
13+
type XFDMutex struct {
1414
fdMutex
1515
}
1616

17-
func (mu *FDMutex) Incref() bool {
17+
func (mu *XFDMutex) Incref() bool {
1818
return mu.incref()
1919
}
2020

21-
func (mu *FDMutex) IncrefAndClose() bool {
21+
func (mu *XFDMutex) IncrefAndClose() bool {
2222
return mu.increfAndClose()
2323
}
2424

25-
func (mu *FDMutex) Decref() bool {
25+
func (mu *XFDMutex) Decref() bool {
2626
return mu.decref()
2727
}
2828

29-
func (mu *FDMutex) RWLock(read bool) bool {
29+
func (mu *XFDMutex) RWLock(read bool) bool {
3030
return mu.rwlock(read)
3131
}
3232

33-
func (mu *FDMutex) RWUnlock(read bool) bool {
33+
func (mu *XFDMutex) RWUnlock(read bool) bool {
3434
return mu.rwunlock(read)
3535
}

Diff for: src/internal/poll/fd_mutex_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
)
1515

1616
func TestMutexLock(t *testing.T) {
17-
var mu FDMutex
17+
var mu XFDMutex
1818

1919
if !mu.Incref() {
2020
t.Fatal("broken")
@@ -39,7 +39,7 @@ func TestMutexLock(t *testing.T) {
3939
}
4040

4141
func TestMutexClose(t *testing.T) {
42-
var mu FDMutex
42+
var mu XFDMutex
4343
if !mu.IncrefAndClose() {
4444
t.Fatal("broken")
4545
}
@@ -60,7 +60,7 @@ func TestMutexClose(t *testing.T) {
6060

6161
func TestMutexCloseUnblock(t *testing.T) {
6262
c := make(chan bool, 4)
63-
var mu FDMutex
63+
var mu XFDMutex
6464
mu.RWLock(true)
6565
for i := 0; i < 4; i++ {
6666
go func() {
@@ -104,7 +104,7 @@ func TestMutexPanic(t *testing.T) {
104104
f()
105105
}
106106

107-
var mu FDMutex
107+
var mu XFDMutex
108108
ensurePanics(func() { mu.Decref() })
109109
ensurePanics(func() { mu.RWUnlock(true) })
110110
ensurePanics(func() { mu.RWUnlock(false) })
@@ -137,7 +137,7 @@ func TestMutexOverflowPanic(t *testing.T) {
137137
}
138138
}()
139139

140-
var mu1 FDMutex
140+
var mu1 XFDMutex
141141
for i := 0; i < 1<<21; i++ {
142142
mu1.Incref()
143143
}
@@ -152,7 +152,7 @@ func TestMutexStress(t *testing.T) {
152152
}
153153
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P))
154154
done := make(chan bool, P)
155-
var mu FDMutex
155+
var mu XFDMutex
156156
var readState [2]uint64
157157
var writeState [2]uint64
158158
for p := 0; p < P; p++ {

Diff for: src/internal/poll/file_plan9.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2022 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+
package poll
6+
7+
// Expose fdMutex for use by the os package on Plan 9.
8+
// On Plan 9 we don't want to use async I/O for file operations,
9+
// but we still want the locking semantics that fdMutex provides.
10+
11+
// FDMutex is an exported fdMutex, only for Plan 9.
12+
type FDMutex struct {
13+
fdmu fdMutex
14+
}
15+
16+
func (fdmu *FDMutex) Incref() bool {
17+
return fdmu.fdmu.incref()
18+
}
19+
20+
func (fdmu *FDMutex) Decref() bool {
21+
return fdmu.fdmu.decref()
22+
}
23+
24+
func (fdmu *FDMutex) IncrefAndClose() bool {
25+
return fdmu.fdmu.increfAndClose()
26+
}
27+
28+
func (fdmu *FDMutex) ReadLock() bool {
29+
return fdmu.fdmu.rwlock(true)
30+
}
31+
32+
func (fdmu *FDMutex) ReadUnlock() bool {
33+
return fdmu.fdmu.rwunlock(true)
34+
}
35+
36+
func (fdmu *FDMutex) WriteLock() bool {
37+
return fdmu.fdmu.rwlock(false)
38+
}
39+
40+
func (fdmu *FDMutex) WriteUnlock() bool {
41+
return fdmu.fdmu.rwunlock(false)
42+
}

Diff for: src/os/file_mutex_plan9.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2022 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+
package os
6+
7+
// File locking support for Plan 9. This uses fdMutex from the
8+
// internal/poll package.
9+
10+
// incref adds a reference to the file. It returns an error if the file
11+
// is already closed. This method is on File so that we can incorporate
12+
// a nil test.
13+
func (f *File) incref(op string) (err error) {
14+
if f == nil {
15+
return ErrInvalid
16+
}
17+
if !f.fdmu.Incref() {
18+
err = ErrClosed
19+
if op != "" {
20+
err = &PathError{Op: op, Path: f.name, Err: err}
21+
}
22+
}
23+
return err
24+
}
25+
26+
// decref removes a reference to the file. If this is the last
27+
// remaining reference, and the file has been marked to be closed,
28+
// then actually close it.
29+
func (file *file) decref() error {
30+
if file.fdmu.Decref() {
31+
return file.destroy()
32+
}
33+
return nil
34+
}
35+
36+
// readLock adds a reference to the file and locks it for reading.
37+
// It returns an error if the file is already closed.
38+
func (file *file) readLock() error {
39+
if !file.fdmu.ReadLock() {
40+
return ErrClosed
41+
}
42+
return nil
43+
}
44+
45+
// readUnlock removes a reference from the file and unlocks it for reading.
46+
// It also closes the file if it marked as closed and there is no remaining
47+
// reference.
48+
func (file *file) readUnlock() {
49+
if file.fdmu.ReadUnlock() {
50+
file.destroy()
51+
}
52+
}
53+
54+
// writeLock adds a reference to the file and locks it for writing.
55+
// It returns an error if the file is already closed.
56+
func (file *file) writeLock() error {
57+
if !file.fdmu.WriteLock() {
58+
return ErrClosed
59+
}
60+
return nil
61+
}
62+
63+
// writeUnlock removes a reference from the file and unlocks it for writing.
64+
// It also closes the file if it is marked as closed and there is no remaining
65+
// reference.
66+
func (file *file) writeUnlock() {
67+
if file.fdmu.WriteUnlock() {
68+
file.destroy()
69+
}
70+
}

Diff for: src/os/file_plan9.go

+66-14
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func fixLongPath(path string) string {
2222
// can overwrite this data, which could cause the finalizer
2323
// to close the wrong file descriptor.
2424
type file struct {
25+
fdmu poll.FDMutex
2526
fd int
2627
name string
2728
dirinfo *dirInfo // nil unless directory being read
@@ -142,24 +143,35 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
142143
// be canceled and return immediately with an ErrClosed error.
143144
// Close will return an error if it has already been called.
144145
func (f *File) Close() error {
145-
if err := f.checkValid("close"); err != nil {
146-
return err
146+
if f == nil {
147+
return ErrInvalid
147148
}
148149
return f.file.close()
149150
}
150151

151152
func (file *file) close() error {
152-
if file == nil || file.fd == badFd {
153-
return ErrInvalid
153+
if !file.fdmu.IncrefAndClose() {
154+
return &PathError{Op: "close", Path: file.name, Err: ErrClosed}
154155
}
156+
157+
// At this point we should cancel any pending I/O.
158+
// How do we do that on Plan 9?
159+
160+
err := file.decref()
161+
162+
// no need for a finalizer anymore
163+
runtime.SetFinalizer(file, nil)
164+
return err
165+
}
166+
167+
// destroy actually closes the descriptor. This is called when
168+
// there are no remaining references, by the decref, readUnlock,
169+
// and writeUnlock methods.
170+
func (file *file) destroy() error {
155171
var err error
156172
if e := syscall.Close(file.fd); e != nil {
157173
err = &PathError{Op: "close", Path: file.name, Err: e}
158174
}
159-
file.fd = badFd // so it can't be closed again
160-
161-
// no need for a finalizer anymore
162-
runtime.SetFinalizer(file, nil)
163175
return err
164176
}
165177

@@ -193,6 +205,12 @@ func (f *File) Truncate(size int64) error {
193205
if err != nil {
194206
return &PathError{Op: "truncate", Path: f.name, Err: err}
195207
}
208+
209+
if err := f.incref("truncate"); err != nil {
210+
return err
211+
}
212+
defer f.decref()
213+
196214
if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
197215
return &PathError{Op: "truncate", Path: f.name, Err: err}
198216
}
@@ -219,6 +237,12 @@ func (f *File) chmod(mode FileMode) error {
219237
if err != nil {
220238
return &PathError{Op: "chmod", Path: f.name, Err: err}
221239
}
240+
241+
if err := f.incref("chmod"); err != nil {
242+
return err
243+
}
244+
defer f.decref()
245+
222246
if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
223247
return &PathError{Op: "chmod", Path: f.name, Err: err}
224248
}
@@ -240,6 +264,12 @@ func (f *File) Sync() error {
240264
if err != nil {
241265
return &PathError{Op: "sync", Path: f.name, Err: err}
242266
}
267+
268+
if err := f.incref("sync"); err != nil {
269+
return err
270+
}
271+
defer f.decref()
272+
243273
if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
244274
return &PathError{Op: "sync", Path: f.name, Err: err}
245275
}
@@ -249,6 +279,10 @@ func (f *File) Sync() error {
249279
// read reads up to len(b) bytes from the File.
250280
// It returns the number of bytes read and an error, if any.
251281
func (f *File) read(b []byte) (n int, err error) {
282+
if err := f.readLock(); err != nil {
283+
return 0, err
284+
}
285+
defer f.readUnlock()
252286
n, e := fixCount(syscall.Read(f.fd, b))
253287
if n == 0 && len(b) > 0 && e == nil {
254288
return 0, io.EOF
@@ -260,6 +294,10 @@ func (f *File) read(b []byte) (n int, err error) {
260294
// It returns the number of bytes read and the error, if any.
261295
// EOF is signaled by a zero count with err set to nil.
262296
func (f *File) pread(b []byte, off int64) (n int, err error) {
297+
if err := f.readLock(); err != nil {
298+
return 0, err
299+
}
300+
defer f.readUnlock()
263301
n, e := fixCount(syscall.Pread(f.fd, b, off))
264302
if n == 0 && len(b) > 0 && e == nil {
265303
return 0, io.EOF
@@ -272,6 +310,10 @@ func (f *File) pread(b []byte, off int64) (n int, err error) {
272310
// Since Plan 9 preserves message boundaries, never allow
273311
// a zero-byte write.
274312
func (f *File) write(b []byte) (n int, err error) {
313+
if err := f.writeLock(); err != nil {
314+
return 0, err
315+
}
316+
defer f.writeUnlock()
275317
if len(b) == 0 {
276318
return 0, nil
277319
}
@@ -283,6 +325,10 @@ func (f *File) write(b []byte) (n int, err error) {
283325
// Since Plan 9 preserves message boundaries, never allow
284326
// a zero-byte write.
285327
func (f *File) pwrite(b []byte, off int64) (n int, err error) {
328+
if err := f.writeLock(); err != nil {
329+
return 0, err
330+
}
331+
defer f.writeUnlock()
286332
if len(b) == 0 {
287333
return 0, nil
288334
}
@@ -294,6 +340,10 @@ func (f *File) pwrite(b []byte, off int64) (n int, err error) {
294340
// relative to the current offset, and 2 means relative to the end.
295341
// It returns the new offset and an error, if any.
296342
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
343+
if err := f.incref(""); err != nil {
344+
return 0, err
345+
}
346+
defer f.decref()
297347
if f.dirinfo != nil {
298348
// Free cached dirinfo, so we allocate a new one if we
299349
// access this file as a directory again. See #35767 and #37161.
@@ -493,9 +543,10 @@ func tempDir() string {
493543
// which must be a directory.
494544
// If there is an error, it will be of type *PathError.
495545
func (f *File) Chdir() error {
496-
if err := f.checkValid("chdir"); err != nil {
546+
if err := f.incref("chdir"); err != nil {
497547
return err
498548
}
549+
defer f.decref()
499550
if e := syscall.Fchdir(f.fd); e != nil {
500551
return &PathError{Op: "chdir", Path: f.name, Err: e}
501552
}
@@ -526,16 +577,17 @@ func (f *File) setWriteDeadline(time.Time) error {
526577
return poll.ErrNoDeadline
527578
}
528579

529-
// checkValid checks whether f is valid for use.
530-
// If not, it returns an appropriate error, perhaps incorporating the operation name op.
580+
// checkValid checks whether f is valid for use, but does not prepare
581+
// to actually use it. If f is not ready checkValid returns an appropriate
582+
// error, perhaps incorporating the operation name op.
531583
func (f *File) checkValid(op string) error {
532584
if f == nil {
533585
return ErrInvalid
534586
}
535-
if f.fd == badFd {
536-
return &PathError{Op: op, Path: f.name, Err: ErrClosed}
587+
if err := f.incref(op); err != nil {
588+
return err
537589
}
538-
return nil
590+
return f.decref()
539591
}
540592

541593
type rawConn struct{}

0 commit comments

Comments
 (0)