Apply read and write deadlines to pending IO

In order to upgrade to go1.8 all IO operations
including previous pending IO must have the deadline
set when SetReadDeadline or SetWriteDeadline is called

Signed-off-by: Darren Stahl <darst@microsoft.com>
This commit is contained in:
Darren Stahl
2017-05-05 14:53:42 -07:00
parent 2a0317319a
commit 899dbf3ba5
2 changed files with 107 additions and 46 deletions
+100 -39
View File
@@ -7,6 +7,7 @@ import (
"io" "io"
"runtime" "runtime"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"time" "time"
) )
@@ -17,6 +18,12 @@ import (
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes //sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod //sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod
type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
const ( const (
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1 cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2 cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
@@ -33,6 +40,8 @@ func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true } func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true } func (e *timeoutError) Temporary() bool { return true }
type timeoutChan chan struct{}
var ioInitOnce sync.Once var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle var ioCompletionPort syscall.Handle
@@ -63,8 +72,16 @@ type win32File struct {
handle syscall.Handle handle syscall.Handle
wg sync.WaitGroup wg sync.WaitGroup
closing bool closing bool
readDeadline time.Time readDeadline deadlineHandler
writeDeadline time.Time writeDeadline deadlineHandler
}
type deadlineHandler struct {
setLock sync.Mutex
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout atomicBool
} }
// makeWin32File makes a new win32File from an existing file handle // makeWin32File makes a new win32File from an existing file handle
@@ -79,6 +96,8 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
f.readDeadline.channel = make(timeoutChan)
f.writeDeadline.channel = make(timeoutChan)
return f, nil return f, nil
} }
@@ -134,53 +153,47 @@ func ioCompletionProcessor(h syscall.Handle) {
// asyncIo processes the return value from ReadFile or WriteFile, blocking until // asyncIo processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed. // the operation has actually completed.
func (f *win32File) asyncIo(c *ioOperation, deadline time.Time, bytes uint32, err error) (int, error) { func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING { if err != syscall.ERROR_IO_PENDING {
f.wg.Done() f.wg.Done()
return int(bytes), err return int(bytes), err
} else { }
var r ioResult
wait := true
timedout := false
if f.closing { if f.closing {
cancelIoEx(f.handle, &c.o) cancelIoEx(f.handle, &c.o)
} else if !deadline.IsZero() { }
now := time.Now()
if !deadline.After(now) { var timeout timeoutChan
timedout = true if d != nil {
} else { d.channelLock.Lock()
timeout := time.After(deadline.Sub(now)) timeout = d.channel
d.channelLock.Unlock()
}
var r ioResult
select { select {
case r = <-c.ch: case r = <-c.ch:
wait = false err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing {
err = ErrFileClosed
}
}
case <-timeout: case <-timeout:
timedout = true
}
}
}
if timedout {
cancelIoEx(f.handle, &c.o) cancelIoEx(f.handle, &c.o)
}
if wait {
r = <-c.ch r = <-c.ch
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
err = ErrTimeout
}
} }
// runtime.KeepAlive is needed, as c is passed via native // runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive // code to ioCompletionProcessor, c must remain alive
// until the channel read is complete. // until the channel read is complete.
runtime.KeepAlive(c) runtime.KeepAlive(c)
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing {
err = ErrFileClosed
} else if timedout {
err = ErrTimeout
}
}
f.wg.Done() f.wg.Done()
return int(r.bytes), err return int(r.bytes), err
}
} }
// Read reads from a file handle. // Read reads from a file handle.
@@ -189,9 +202,14 @@ func (f *win32File) Read(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
if f.readDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32 var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o) err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, f.readDeadline, bytes, err) n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b) runtime.KeepAlive(b)
// Handle EOF conditions. // Handle EOF conditions.
@@ -210,23 +228,66 @@ func (f *win32File) Write(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
if f.writeDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32 var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o) err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, f.writeDeadline, bytes, err) n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b) runtime.KeepAlive(b)
return n, err return n, err
} }
func (f *win32File) SetReadDeadline(t time.Time) error { func (f *win32File) SetReadDeadline(deadline time.Time) error {
f.readDeadline = t return f.readDeadline.set(deadline)
return nil
} }
func (f *win32File) SetWriteDeadline(t time.Time) error { func (f *win32File) SetWriteDeadline(deadline time.Time) error {
f.writeDeadline = t return f.writeDeadline.set(deadline)
return nil
} }
func (f *win32File) Flush() error { func (f *win32File) Flush() error {
return syscall.FlushFileBuffers(f.handle) return syscall.FlushFileBuffers(f.handle)
} }
func (d *deadlineHandler) set(deadline time.Time) error {
d.setLock.Lock()
defer d.setLock.Unlock()
if d.timer != nil {
if !d.timer.Stop() {
<-d.channel
}
d.timer = nil
}
d.timedout.setFalse()
select {
case <-d.channel:
d.channelLock.Lock()
d.channel = make(chan struct{})
d.channelLock.Unlock()
default:
}
if deadline.IsZero() {
return nil
}
timeoutIO := func() {
d.timedout.setTrue()
close(d.channel)
}
now := time.Now()
duration := deadline.Sub(now)
if deadline.After(now) {
// Deadline is in the future, set a timer to wait
d.timer = time.AfterFunc(duration, timeoutIO)
} else {
// Deadline is in the past. Cancel all pending IO now.
timeoutIO()
}
return nil
}
+1 -1
View File
@@ -367,7 +367,7 @@ func connectPipe(p *win32File) error {
return err return err
} }
err = connectNamedPipe(p.handle, &c.o) err = connectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, time.Time{}, 0, err) _, err = p.asyncIo(c, nil, 0, err)
if err != nil && err != cERROR_PIPE_CONNECTED { if err != nil && err != cERROR_PIPE_CONNECTED {
return err return err
} }