// It supports both synchronous and asynchronous IO.
func execIO(o *operation, submit func(o *operation) error) (int, error) {
fd := o.fd
+ fd.initIO()
// Notify runtime netpoll about starting IO.
err := fd.pd.prepare(int(o.mode), fd.isFile)
if err != nil {
// The kind of this file.
kind fileKind
- // Whether FILE_FLAG_OVERLAPPED was not set when opening the file
+ // Whether FILE_FLAG_OVERLAPPED was not set when opening the file.
isBlocking bool
+
+ // Initialization parameters.
+ initIOOnce sync.Once
+ initIOErr error // only used in the net package
+ initPollable bool // value passed to [FD.Init]
}
// setOffset sets the offset fields of the overlapped object
)
// logInitFD is set by tests to enable file descriptor initialization logging.
-var logInitFD func(net string, fd *FD, err error)
+var logInitFD func(net int, fd *FD, err error)
+
+func (fd *FD) initIO() error {
+ fd.initIOOnce.Do(func() {
+ if fd.initPollable {
+ // 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.
+ //
+ // If we could not add the handle to the runtime poller,
+ // assume the handle hasn't been opened for overlapped I/O.
+ fd.initIOErr = fd.pd.init(fd)
+ if fd.initIOErr != nil {
+ fd.initPollable = false
+ }
+ }
+ if logInitFD != nil {
+ logInitFD(int(fd.kind), fd, fd.initIOErr)
+ }
+ if !fd.initPollable {
+ // Handle opened for overlapped I/O (aka non-blocking) that are not added
+ // to the runtime poller need special handling when reading and writing.
+ var info windows.FILE_MODE_INFORMATION
+ if err := windows.NtQueryInformationFile(fd.Sysfd, &windows.IO_STATUS_BLOCK{}, uintptr(unsafe.Pointer(&info)), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err == nil {
+ fd.isBlocking = info.Mode&(windows.FILE_SYNCHRONOUS_IO_ALERT|windows.FILE_SYNCHRONOUS_IO_NONALERT) != 0
+ } else {
+ // If we fail to get the file mode information, assume the file is blocking.
+ fd.isBlocking = true
+ }
+ } else {
+ fd.rop.runtimeCtx = fd.pd.runtimeCtx
+ fd.wop.runtimeCtx = fd.pd.runtimeCtx
+ if fd.kind != kindNet || socketCanUseSetFileCompletionNotificationModes {
+ // Non-socket handles can use SetFileCompletionNotificationModes without problems.
+ err := syscall.SetFileCompletionNotificationModes(fd.Sysfd,
+ syscall.FILE_SKIP_SET_EVENT_ON_HANDLE|syscall.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS,
+ )
+ if err == nil {
+ fd.skipSyncNotif = true
+ }
+ }
+ }
+ })
+ return fd.initIOErr
+}
// Init initializes the FD. The Sysfd field should already be set.
// This can be called multiple times on a single FD.
}
switch net {
- case "file", "dir":
+ case "file":
fd.kind = kindFile
case "console":
fd.kind = kindConsole
case "pipe":
fd.kind = kindPipe
- case "tcp", "tcp4", "tcp6",
- "udp", "udp4", "udp6",
- "ip", "ip4", "ip6",
- "unix", "unixgram", "unixpacket":
- fd.kind = kindNet
default:
- return errors.New("internal error: unknown network type " + net)
+ // We don't actually care about the various network types.
+ fd.kind = kindNet
}
fd.isFile = fd.kind != kindNet
+ fd.initPollable = pollable
fd.rop.mode = 'r'
fd.wop.mode = 'w'
fd.rop.fd = fd
fd.wop.fd = fd
- var err error
- if pollable {
- // Note that 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.
- //
- // If we could not add the handle to the runtime poller,
- // assume the handle hasn't been opened for overlapped I/O.
- err = fd.pd.init(fd)
- pollable = err == nil
- }
- if logInitFD != nil {
- logInitFD(net, fd, err)
- }
- if !pollable {
- // Handle opened for overlapped I/O (aka non-blocking) that are not added
- // to the runtime poller need special handling when reading and writing.
- var info windows.FILE_MODE_INFORMATION
- if err := windows.NtQueryInformationFile(fd.Sysfd, &windows.IO_STATUS_BLOCK{}, uintptr(unsafe.Pointer(&info)), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err == nil {
- fd.isBlocking = info.Mode&(windows.FILE_SYNCHRONOUS_IO_ALERT|windows.FILE_SYNCHRONOUS_IO_NONALERT) != 0
- } else {
- // If we fail to get the file mode information, assume the file is blocking.
- fd.isBlocking = true
- }
- return err
- }
- if fd.kind != kindNet || socketCanUseSetFileCompletionNotificationModes {
- // Non-socket handles can use SetFileCompletionNotificationModes without problems.
- err := syscall.SetFileCompletionNotificationModes(fd.Sysfd,
- syscall.FILE_SKIP_SET_EVENT_ON_HANDLE|syscall.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS,
- )
- if err == nil {
- fd.skipSyncNotif = true
+ // A file handle (and its duplicated handles) can only be associated
+ // with one IOCP. A new association will fail if the handle is already
+ // associated. Defer the association until the first I/O operation so that
+ // overlapped handles passed in os.NewFile have a chance to be used
+ // with an external IOCP. This is common case, for example, when calling
+ // os.NewFile on a handle just to pass it to a exec.Command standard
+ // input/output/error. If the association fails, the I/O operations
+ // will be performed synchronously.
+ if fd.kind == kindNet {
+ // The net package is the only consumer that requires overlapped
+ // handles and that cares about handle IOCP association errors.
+ // We can should do the IOCP association here.
+ return fd.initIO()
+ } else {
+ if logInitFD != nil {
+ // For testing.
+ logInitFD(int(fd.kind), fd, nil)
}
}
- fd.rop.runtimeCtx = fd.pd.runtimeCtx
- fd.wop.runtimeCtx = fd.pd.runtimeCtx
return nil
}
)
type loggedFD struct {
- Net string
+ Net int
FD *poll.FD
Err error
}
loggedFDs map[syscall.Handle]*loggedFD
)
-func logFD(net string, fd *poll.FD, err error) {
+func logFD(net int, fd *poll.FD, err error) {
logMu.Lock()
defer logMu.Unlock()
if overlapped && err != nil {
// Overlapped file handles should not error.
t.Fatal(err)
- } else if !overlapped && pollable && err == nil {
- // Non-overlapped file handles should return an error but still
- // be usable as sync handles.
- t.Fatal("expected error for non-overlapped file handle")
}
return &fd
}
}
}
+func TestPipeReadTimeout(t *testing.T) {
+ t.Parallel()
+ name := pipeName()
+ _ = newBytePipe(t, name, true, true)
+ file := newFile(t, name, true, true)
+
+ err := file.SetReadDeadline(time.Now().Add(time.Millisecond))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var buf [10]byte
+ _, err = file.Read(buf[:])
+ if err != poll.ErrDeadlineExceeded {
+ t.Errorf("expected deadline exceeded, got %v", err)
+ }
+}
+
func TestPipeCanceled(t *testing.T) {
t.Parallel()
name := pipeName()
}
}
+func TestPipeExternalIOCP(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.
+ t.Parallel()
+ name := pipeName()
+ pipe := newMessagePipe(t, name, true, true)
+ _ = newFile(t, name, true, true) // Just open a pipe client
+
+ _, err := windows.CreateIoCompletionPort(syscall.Handle(pipe.Sysfd), 0, 0, 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = pipe.Write([]byte("hello"))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
func BenchmarkReadOverlapped(b *testing.B) {
benchmarkRead(b, true)
}
procProcessPrng = modbcryptprimitives.NewProc("ProcessPrng")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procCreateEventW = modkernel32.NewProc("CreateEventW")
+ procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procGetACP = modkernel32.NewProc("GetACP")
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
return
}
+func CreateIoCompletionPort(filehandle syscall.Handle, cphandle syscall.Handle, key uintptr, threadcnt uint32) (handle syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(filehandle), uintptr(cphandle), uintptr(key), uintptr(threadcnt), 0, 0)
+ handle = syscall.Handle(r0)
+ if handle == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
func CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
handle = syscall.Handle(r0)