internal/poll: fix race in Close

There is a potential race between a concurrent call to FD.initIO, which
calls FD.pd.init, and a call to FD.Close, which calls FD.pd.evict.

This is solved by calling FD.initIO in FD.Close, as that will block
until the concurrent FD.initIO has completed. Note that FD.initIO is
no-op if first called from here.

The race window is so small that it is not possible to write a test
that triggers it.

Change-Id: Ie2f2818e746b9d626fe3b9eb6b8ff967c81ef863
Reviewed-on: https://go-review.googlesource.com/c/go/+/663815
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
qmuntal 2025-04-08 15:31:02 +02:00 committed by Quim Muntal
parent 7007dfcd0c
commit cac276f81a

View File

@ -336,6 +336,10 @@ func (fd *FD) initIO() error {
return nil
}
fd.initIOOnce.Do(func() {
if fd.closing() {
// Closing, nothing to do.
return
}
// The runtime poller will ignore I/O completion
// notifications not initiated by this package,
// so it is safe to add handles owned by the caller.
@ -434,6 +438,12 @@ func (fd *FD) Close() error {
if !fd.fdmu.increfAndClose() {
return errClosing(fd.isFile)
}
// There is a potential race between a concurrent call to fd.initIO,
// which calls fd.pd.init, and the call to fd.pd.evict below.
// This is solved by calling fd.initIO ourselves, which will
// block until the concurrent fd.initIO has completed. Note
// that fd.initIO is no-op if first called from here.
fd.initIO()
if fd.kind == kindPipe {
syscall.CancelIoEx(fd.Sysfd, nil)
}