internal/poll: readWriteUnlock should destroy fd when no remaining references

Fixes #77404

Change-Id: I0402becb94855baf942d6ba3815cc2a3c1526d6e
Reviewed-on: https://go-review.googlesource.com/c/go/+/740921
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
qmuntal 2026-02-02 12:06:22 +01:00 committed by Quim Muntal
parent 18d31e3e8b
commit 31c9bcb103
2 changed files with 31 additions and 1 deletions

View file

@ -265,7 +265,9 @@ func (fd *FD) readWriteLock() error {
// is no remaining reference.
func (fd *FD) readWriteUnlock() {
fd.fdmu.rwunlock(true)
fd.fdmu.rwunlock(false)
if fd.fdmu.rwunlock(false) {
fd.destroy()
}
}
// closing returns true if fd is closing.

View file

@ -3468,6 +3468,34 @@ func TestWriteStringAlloc(t *testing.T) {
}
}
// Test that it's OK to have parallel I/O and Close on a file.
func TestFileIOCloseRace(t *testing.T) {
t.Parallel()
file, err := Create(filepath.Join(t.TempDir(), "test.txt"))
if err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
wg.Go(func() {
var tmp [100]byte
if _, err := file.Write(tmp[:]); err != nil && !errors.Is(err, ErrClosed) {
t.Error(err)
}
})
wg.Go(func() {
var tmp [100]byte
if _, err := file.Read(tmp[:]); err != nil && err != io.EOF && !errors.Is(err, ErrClosed) {
t.Error(err)
}
})
wg.Go(func() {
if err := file.Close(); err != nil {
t.Error(err)
}
})
wg.Wait()
}
// Test that it's OK to have parallel I/O and Close on a pipe.
func TestPipeIOCloseRace(t *testing.T) {
// Skip on wasm, which doesn't have pipes.