"internal/syscall/windows"
"io"
"sync"
+ "sync/atomic"
"syscall"
"unicode/utf16"
"unicode/utf8"
o.o.HEvent = h | 1
}
+func (o *operation) close() {
+ if o.o.HEvent != 0 {
+ syscall.CloseHandle(o.o.HEvent)
+ }
+}
+
func (o *operation) overlapped() *syscall.Overlapped {
if o.fd.isBlocking {
// Don't return the overlapped object if the file handle
panic("can't wait on blocking operations")
}
fd := o.fd
- if !fd.pd.pollable() {
+ if !fd.pollable() {
// The overlapped handle is not added to the runtime poller,
// the only way to wait for the IO to complete is block until
// the overlapped event is signaled.
// cancelIO cancels the IO operation o and waits for it to complete.
func cancelIO(o *operation) {
fd := o.fd
- if !fd.pd.pollable() {
+ if !fd.pollable() {
return
}
// Cancel our request.
// to avoid reusing the values from a previous call.
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 {
return 0, err
}
// Start IO.
- if !fd.isBlocking && o.o.HEvent == 0 && !fd.pd.pollable() {
+ if !fd.isBlocking && o.o.HEvent == 0 && !fd.pollable() {
// If the handle is opened for overlapped IO but we can't
// use the runtime poller, then we need to use an
// event to wait for the IO to complete.
err = windows.WSAGetOverlappedResult(fd.Sysfd, &o.o, &o.qty, false, &o.flags)
}
}
- // ERROR_OPERATION_ABORTED may have been caused by us. In that case,
- // map it to our own error. Don't do more than that, each submitted
- // function may have its own meaning for each error.
- if err == syscall.ERROR_OPERATION_ABORTED {
+ switch err {
+ case syscall.ERROR_OPERATION_ABORTED:
+ // ERROR_OPERATION_ABORTED may have been caused by us. In that case,
+ // map it to our own error. Don't do more than that, each submitted
+ // function may have its own meaning for each error.
if waitErr != nil {
// IO canceled by the poller while waiting for completion.
err = waitErr
// we assume it is interrupted by Close.
err = errClosing(fd.isFile)
}
+ case windows.ERROR_IO_INCOMPLETE:
+ // waitIO couldn't wait for the IO to complete.
+ if waitErr != nil {
+ // The wait error will be more informative.
+ err = waitErr
+ }
}
return int(o.qty), err
}
// 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
+ disassociated atomic.Bool
}
// setOffset sets the offset fields of the overlapped object
fd.setOffset(fd.offset + int64(off))
}
+// pollable should be used instead of fd.pd.pollable(),
+// as it is aware of the disassociated state.
+func (fd *FD) pollable() bool {
+ return fd.pd.pollable() && !fd.disassociated.Load()
+}
+
// fileKind describes the kind of file.
type fileKind byte
kindPipe
)
-func (fd *FD) initIO() error {
- if fd.isBlocking {
- 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.
- fd.initIOErr = fd.pd.init(fd)
- if fd.initIOErr != nil {
- return
- }
- 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,
- )
- fd.skipSyncNotif = err == nil
- }
- })
- return fd.initIOErr
-}
-
// Init initializes the FD. The Sysfd field should already be set.
// This can be called multiple times on a single FD.
// The net argument is a network name from the net package (e.g., "tcp"),
fd.rop.fd = fd
fd.wop.fd = fd
- // 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()
+ // It is safe to add overlapped handles that also perform I/O
+ // outside of the runtime poller. The runtime poller will ignore
+ // I/O completion notifications not initiated by us.
+ err := fd.pd.init(fd)
+ if err != nil {
+ return err
+ }
+ 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,
+ )
+ fd.skipSyncNotif = err == nil
+ }
+ return nil
+}
+
+// DisassociateIOCP disassociates the file handle from the IOCP.
+// The disassociate operation will not succeed if there is any
+// in-progress IO operation on the file handle.
+func (fd *FD) DisassociateIOCP() error {
+ if err := fd.incref(); err != nil {
+ return err
+ }
+ defer fd.decref()
+
+ if fd.isBlocking || !fd.pollable() {
+ // Nothing to disassociate.
+ return nil
}
+
+ info := windows.FILE_COMPLETION_INFORMATION{}
+ if err := windows.NtSetInformationFile(fd.Sysfd, &windows.IO_STATUS_BLOCK{}, unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), windows.FileReplaceCompletionInformation); err != nil {
+ return err
+ }
+ fd.disassociated.Store(true)
+ // Don't call fd.pd.close(), it would be too racy.
+ // There is no harm on leaving fd.pd open until Close is called.
return nil
}
if fd.Sysfd == syscall.InvalidHandle {
return syscall.EINVAL
}
+ fd.rop.close()
+ fd.wop.close()
// Poller may want to unregister fd in readiness notification mechanism,
// so this must be executed before fd.CloseFunc.
fd.pd.close()
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)
}
}
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
- }
+ _, err := windows.CreateIoCompletionPort(syscall.Handle(f.Fd()), iocp, 0, 0)
return err
}
}
}
+func TestFileWriteFdRace(t *testing.T) {
+ t.Parallel()
+
+ f := newFileOverlapped(t, filepath.Join(t.TempDir(), "a"), true)
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+ n, err := f.Write([]byte("hi"))
+ if err != nil {
+ // We look at error strings as the
+ // expected errors are OS-specific.
+ switch {
+ case errors.Is(err, windows.ERROR_INVALID_HANDLE):
+ // Ignore an expected error.
+ default:
+ // Unexpected error.
+ t.Error(err)
+ }
+ return
+ }
+ if n != 2 {
+ t.Errorf("wrote %d bytes, expected 2", n)
+ return
+ }
+ }()
+ go func() {
+ defer wg.Done()
+ f.Fd()
+ }()
+ wg.Wait()
+
+ iocp, err := windows.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer syscall.CloseHandle(iocp)
+ if err := iocpAssociateFile(f, iocp); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := f.Write([]byte("hi")); err != nil {
+ t.Error(err)
+ }
+}
+
func TestSplitPath(t *testing.T) {
t.Parallel()
for _, tt := range []struct{ path, wantDir, wantBase string }{