go-winio: Prevent Data Race when accessing closing
The race usually happens when `closeHandle()` and `prepareIo()` are called
concurrently; the former tries to set `closing` to `true` the latter tries
to read its value.
In order to avoid this issue, we added a lock around the variable.
@@ -69,6 +69,7 @@ func initIo() {
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
// It takes ownership of this handle and will close it if it is garbage collected.
type win32File struct {
+ sync.Mutex
handle syscall.Handle
wg sync.WaitGroup
closing bool
@@ -105,17 +106,28 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
return makeWin32File(h)
}
+func (f *win32File) isClosing() bool {
+ f.Lock()
+ closing := f.closing
+ f.Unlock()
+ return closing
+}
+
// closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() {
+ f.Lock()
if !f.closing {
// cancel all IO and wait for it to complete
f.closing = true
+ f.Unlock()
cancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
syscall.Close(f.handle)
f.handle = 0
+ return
}
+ f.Unlock()
}
// Close closes a win32File.
@@ -128,7 +140,7 @@ func (f *win32File) Close() error {
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) {
f.wg.Add(1)
- if f.closing {
+ if f.isClosing() {
return nil, ErrFileClosed
}
c := &ioOperation{}
@@ -159,7 +171,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
return int(bytes), err
}
- if f.closing {
+ if f.isClosing() {
cancelIoEx(f.handle, &c.o)
}
@@ -175,7 +187,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
case r = <-c.ch:
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
- if f.closing {
+ if f.isClosing() {
err = ErrFileClosed
}
}
