Skip to content

Commit f414dfe

Browse files
qmuntalgopherbot
authored andcommitted
os,internal/poll: support I/O on overlapped files not added to the poller
This fixes the support for I/O on overlapped files that are not added to the poller. Note that CL 661795 already added support for that, but it really only worked for pipes, not for plain files. Additionally, this CL also makes this kind of I/O operations to not notify the external poller to avoid confusing it. Updates #15388. Change-Id: I15c6ea74f3a87960aef0986598077b6eab9b9c99 Reviewed-on: https://go-review.googlesource.com/c/go/+/664415 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Reviewed-by: Alex Brainman <[email protected]> Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Quim Muntal <[email protected]>
1 parent 13b7c7d commit f414dfe

File tree

2 files changed

+102
-24
lines changed

2 files changed

+102
-24
lines changed

src/internal/poll/fd_windows.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ var InitWSA = sync.OnceFunc(func() {
6868
// operation contains superset of data necessary to perform all async IO.
6969
type operation struct {
7070
// Used by IOCP interface, it must be first field
71-
// of the struct, as our code rely on it.
71+
// of the struct, as our code relies on it.
7272
o syscall.Overlapped
7373

7474
// fields used by runtime.netpoll
@@ -88,6 +88,16 @@ type operation struct {
8888
bufs []syscall.WSABuf
8989
}
9090

91+
func (o *operation) setEvent() {
92+
h, err := windows.CreateEvent(nil, 0, 0, nil)
93+
if err != nil {
94+
// This shouldn't happen when all CreateEvent arguments are zero.
95+
panic(err)
96+
}
97+
// Set the low bit so that the external IOCP doesn't receive the completion packet.
98+
o.o.HEvent = h | 1
99+
}
100+
91101
func (o *operation) overlapped() *syscall.Overlapped {
92102
if o.fd.isBlocking {
93103
// Don't return the overlapped object if the file handle
@@ -155,11 +165,15 @@ func (o *operation) InitMsg(p []byte, oob []byte) {
155165

156166
// waitIO waits for the IO operation o to complete.
157167
func waitIO(o *operation) error {
168+
if o.fd.isBlocking {
169+
panic("can't wait on blocking operations")
170+
}
158171
fd := o.fd
159172
if !fd.pd.pollable() {
160173
// The overlapped handle is not added to the runtime poller,
161-
// the only way to wait for the IO to complete is block.
162-
_, err := syscall.WaitForSingleObject(fd.Sysfd, syscall.INFINITE)
174+
// the only way to wait for the IO to complete is block until
175+
// the overlapped event is signaled.
176+
_, err := syscall.WaitForSingleObject(o.o.HEvent, syscall.INFINITE)
163177
return err
164178
}
165179
// Wait for our request to complete.
@@ -202,11 +216,19 @@ func execIO(o *operation, submit func(o *operation) error) (int, error) {
202216
return 0, err
203217
}
204218
// Start IO.
219+
if !fd.isBlocking && o.o.HEvent == 0 && !fd.pd.pollable() {
220+
// If the handle is opened for overlapped IO but we can't
221+
// use the runtime poller, then we need to use an
222+
// event to wait for the IO to complete.
223+
o.setEvent()
224+
}
205225
o.qty = 0
206226
o.flags = 0
207227
err = submit(o)
208228
var waitErr error
209-
if err == syscall.ERROR_IO_PENDING || (err == nil && !o.fd.skipSyncNotif) {
229+
// Blocking operations shouldn't return ERROR_IO_PENDING.
230+
// Continue without waiting if that happens.
231+
if !o.fd.isBlocking && (err == syscall.ERROR_IO_PENDING || (err == nil && !o.fd.skipSyncNotif)) {
210232
// IO started asynchronously or completed synchronously but
211233
// a sync notification is required. Wait for it to complete.
212234
waitErr = waitIO(o)
@@ -345,11 +367,6 @@ func (fd *FD) initIO() error {
345367
// so it is safe to add handles owned by the caller.
346368
fd.initIOErr = fd.pd.init(fd)
347369
if fd.initIOErr != nil {
348-
// This can happen if the handle is already associated
349-
// with another IOCP or if the isBlocking flag is incorrect.
350-
// In both cases, fallback to synchronous IO.
351-
fd.isBlocking = true
352-
fd.skipSyncNotif = true
353370
return
354371
}
355372
fd.rop.runtimeCtx = fd.pd.runtimeCtx
@@ -389,7 +406,6 @@ func (fd *FD) Init(net string, pollable bool) error {
389406
}
390407
fd.isFile = fd.kind != kindNet
391408
fd.isBlocking = !pollable
392-
fd.skipSyncNotif = fd.isBlocking
393409
fd.rop.mode = 'r'
394410
fd.wop.mode = 'w'
395411
fd.rop.fd = fd

src/os/os_windows_test.go

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,31 +1984,93 @@ func TestPipeCanceled(t *testing.T) {
19841984
}
19851985
}
19861986

1987-
func TestPipeExternalIOCP(t *testing.T) {
1987+
func iocpAssociateFile(f *os.File, iocp syscall.Handle) error {
1988+
sc, err := f.SyscallConn()
1989+
if err != nil {
1990+
return err
1991+
}
1992+
var syserr error
1993+
err = sc.Control(func(fd uintptr) {
1994+
if _, err = windows.CreateIoCompletionPort(syscall.Handle(fd), iocp, 0, 0); err != nil {
1995+
syserr = err
1996+
}
1997+
})
1998+
if err == nil {
1999+
err = syserr
2000+
}
2001+
return err
2002+
}
2003+
2004+
func TestFileAssociatedWithExternalIOCP(t *testing.T) {
19882005
// Test that a caller can associate an overlapped handle to an external IOCP
1989-
// even when the handle is also associated to a poll.FD. Also test that
1990-
// the FD can still perform I/O after the association.
2006+
// after the handle has been passed to os.NewFile.
2007+
// Also test that the File can perform I/O after it is associated with the
2008+
// external IOCP and that those operations do not post to the external IOCP.
19912009
t.Parallel()
19922010
name := pipeName()
19932011
pipe := newMessagePipe(t, name, true)
1994-
_ = newFileOverlapped(t, name, true) // Just open a pipe client
2012+
_ = newFileOverlapped(t, name, true) // just open a pipe client
2013+
2014+
// Use a file to exercise WriteAt.
2015+
file := newFileOverlapped(t, filepath.Join(t.TempDir(), "a"), true)
19952016

1996-
sc, err := pipe.SyscallConn()
2017+
iocp, err := windows.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
19972018
if err != nil {
1998-
t.Error(err)
1999-
return
2019+
t.Fatal(err)
20002020
}
2001-
if err := sc.Control(func(fd uintptr) {
2002-
_, err := windows.CreateIoCompletionPort(syscall.Handle(fd), 0, 0, 1)
2003-
if err != nil {
2021+
defer func() {
2022+
if iocp == syscall.InvalidHandle {
2023+
// Already closed at the end of the test.
2024+
return
2025+
}
2026+
if err := syscall.CloseHandle(iocp); err != nil {
20042027
t.Fatal(err)
20052028
}
2006-
}); err != nil {
2007-
t.Error(err)
2029+
}()
2030+
2031+
ch := make(chan error, 1)
2032+
go func() {
2033+
var bytes, key uint32
2034+
var overlapped *syscall.Overlapped
2035+
err := syscall.GetQueuedCompletionStatus(syscall.Handle(iocp), &bytes, &key, &overlapped, syscall.INFINITE)
2036+
ch <- err
2037+
}()
2038+
2039+
if err := iocpAssociateFile(pipe, iocp); err != nil {
2040+
t.Fatal(err)
2041+
}
2042+
if err := iocpAssociateFile(file, iocp); err != nil {
2043+
t.Fatal(err)
20082044
}
20092045

2010-
_, err = pipe.Write([]byte("hello"))
2011-
if err != nil {
2046+
if _, err := pipe.Write([]byte("hello")); err != nil {
2047+
t.Fatal(err)
2048+
}
2049+
if _, err := file.Write([]byte("hello")); err != nil {
20122050
t.Fatal(err)
20132051
}
2052+
if _, err := file.WriteAt([]byte("hello"), 0); err != nil {
2053+
t.Fatal(err)
2054+
}
2055+
2056+
// Wait fot he goroutine to call GetQueuedCompletionStatus.
2057+
time.Sleep(100 * time.Millisecond)
2058+
2059+
// Trigger ERROR_ABANDONED_WAIT_0.
2060+
if err := syscall.CloseHandle(iocp); err != nil {
2061+
t.Fatal(err)
2062+
}
2063+
2064+
// Wait for the completion to be posted to the IOCP.
2065+
err = <-ch
2066+
iocp = syscall.InvalidHandle
2067+
const ERROR_ABANDONED_WAIT_0 = syscall.Errno(735)
2068+
switch err {
2069+
case ERROR_ABANDONED_WAIT_0:
2070+
// This is what we expect.
2071+
case nil:
2072+
t.Error("unexpected queued completion")
2073+
default:
2074+
t.Error(err)
2075+
}
20142076
}

0 commit comments

Comments
 (0)