From f414dfe4f5049c2c8998b4e6b90dee7fca0c225b Mon Sep 17 00:00:00 2001 From: qmuntal Date: Thu, 10 Apr 2025 16:03:46 +0200 Subject: 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 Reviewed-by: Michael Pratt Reviewed-by: Alex Brainman Reviewed-by: Damien Neil Auto-Submit: Quim Muntal --- src/os/os_windows_test.go | 90 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 14 deletions(-) (limited to 'src/os/os_windows_test.go') diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index 5fbf987291..15f1b616e6 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -1984,31 +1984,93 @@ func TestPipeCanceled(t *testing.T) { } } -func TestPipeExternalIOCP(t *testing.T) { +func iocpAssociateFile(f *os.File, iocp syscall.Handle) error { + sc, err := f.SyscallConn() + if err != nil { + return err + } + var syserr error + err = sc.Control(func(fd uintptr) { + if _, err = windows.CreateIoCompletionPort(syscall.Handle(fd), iocp, 0, 0); err != nil { + syserr = err + } + }) + if err == nil { + err = syserr + } + return err +} + +func TestFileAssociatedWithExternalIOCP(t *testing.T) { // Test that a caller can associate an overlapped handle to an external IOCP - // even when the handle is also associated to a poll.FD. Also test that - // the FD can still perform I/O after the association. + // after the handle has been passed to os.NewFile. + // Also test that the File can perform I/O after it is associated with the + // external IOCP and that those operations do not post to the external IOCP. t.Parallel() name := pipeName() pipe := newMessagePipe(t, name, true) - _ = newFileOverlapped(t, name, true) // Just open a pipe client + _ = newFileOverlapped(t, name, true) // just open a pipe client + + // Use a file to exercise WriteAt. + file := newFileOverlapped(t, filepath.Join(t.TempDir(), "a"), true) - sc, err := pipe.SyscallConn() + iocp, err := windows.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) if err != nil { - t.Error(err) - return + t.Fatal(err) } - if err := sc.Control(func(fd uintptr) { - _, err := windows.CreateIoCompletionPort(syscall.Handle(fd), 0, 0, 1) - if err != nil { + defer func() { + if iocp == syscall.InvalidHandle { + // Already closed at the end of the test. + return + } + if err := syscall.CloseHandle(iocp); err != nil { t.Fatal(err) } - }); err != nil { - t.Error(err) + }() + + ch := make(chan error, 1) + go func() { + var bytes, key uint32 + var overlapped *syscall.Overlapped + err := syscall.GetQueuedCompletionStatus(syscall.Handle(iocp), &bytes, &key, &overlapped, syscall.INFINITE) + ch <- err + }() + + if err := iocpAssociateFile(pipe, iocp); err != nil { + t.Fatal(err) + } + if err := iocpAssociateFile(file, iocp); err != nil { + t.Fatal(err) } - _, err = pipe.Write([]byte("hello")) - if err != nil { + if _, err := pipe.Write([]byte("hello")); err != nil { + t.Fatal(err) + } + if _, err := file.Write([]byte("hello")); err != nil { t.Fatal(err) } + if _, err := file.WriteAt([]byte("hello"), 0); err != nil { + t.Fatal(err) + } + + // Wait fot he goroutine to call GetQueuedCompletionStatus. + time.Sleep(100 * time.Millisecond) + + // Trigger ERROR_ABANDONED_WAIT_0. + if err := syscall.CloseHandle(iocp); err != nil { + t.Fatal(err) + } + + // Wait for the completion to be posted to the IOCP. + err = <-ch + iocp = syscall.InvalidHandle + const ERROR_ABANDONED_WAIT_0 = syscall.Errno(735) + switch err { + case ERROR_ABANDONED_WAIT_0: + // This is what we expect. + case nil: + t.Error("unexpected queued completion") + default: + t.Error(err) + } } -- cgit v1.3