diff --git a/file.go b/file.go index 0353160..e303a5d 100644 --- a/file.go +++ b/file.go @@ -19,7 +19,17 @@ const ( fileSkipSetEventOnHandle = 2 ) -var FileClosed = errors.New("File has already been closed.") +var ( + ErrFileClosed = errors.New("file has already been closed") + ErrTimeout = &timeoutError{} +) + +type timeoutError struct{} + +func (e *timeoutError) Error() string { return "i/o timeout" } +func (e *timeoutError) Timeout() bool { return true } +func (e *timeoutError) Temporary() bool { return true } + var ioInitOnce sync.Once var ioCompletionPort syscall.Handle @@ -98,7 +108,7 @@ func (f *win32File) Close() error { func (f *win32File) prepareIo() (*ioOperation, error) { f.wg.Add(1) if f.closing { - return nil, FileClosed + return nil, ErrFileClosed } c := &ioOperation{} c.ch = make(chan ioResult) @@ -150,12 +160,17 @@ func (f *win32File) asyncIo(c *ioOperation, deadline time.Time, bytes uint32, er } if wait { r = <-c.ch - if timedout && r.err == syscall.ERROR_OPERATION_ABORTED { - r.err = syscall.ETIMEDOUT + } + err = r.err + if err == syscall.ERROR_OPERATION_ABORTED { + if f.closing { + err = ErrFileClosed + } else if timedout { + err = ErrTimeout } } f.wg.Done() - return int(r.bytes), r.err + return int(r.bytes), err } } diff --git a/pipe.go b/pipe.go index 058a2e1..b438a10 100644 --- a/pipe.go +++ b/pipe.go @@ -1,6 +1,7 @@ package winio import ( + "errors" "net" "os" "syscall" @@ -28,6 +29,11 @@ const ( pipeUnlimitedInstances = 255 ) +var ( + // This error should match net.errClosing since docker takes a dependency on its text + ErrPipeListenerClosed = errors.New("use of closed network connection") +) + type win32Pipe struct { *win32File path string @@ -65,7 +71,7 @@ func makeWin32Pipe(h syscall.Handle, path string) (*win32Pipe, error) { return &win32Pipe{f, path}, nil } -func DialPipe(s string, timeout *time.Duration) (net.Conn, error) { +func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { var absTimeout time.Time if timeout != nil { absTimeout = time.Now().Add(*timeout) @@ -73,7 +79,7 @@ func DialPipe(s string, timeout *time.Duration) (net.Conn, error) { var err error var h syscall.Handle for { - h, err = createFile(s, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED, 0) + h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED, 0) if err != cERROR_PIPE_BUSY { break } @@ -86,18 +92,18 @@ func DialPipe(s string, timeout *time.Duration) (net.Conn, error) { } else { ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000) } - err = waitNamedPipe(s, ms) + err = waitNamedPipe(path, ms) if err != nil { if err == cERROR_SEM_TIMEOUT { - return nil, syscall.ETIMEDOUT + return nil, ErrTimeout } break } } if err != nil { - return nil, &os.PathError{"open", s, err} + return nil, &os.PathError{"open", path, err} } - p, err := makeWin32Pipe(h, s) + p, err := makeWin32Pipe(h, path) if err != nil { syscall.CloseHandle(h) return nil, err @@ -182,8 +188,8 @@ func (l *win32PipeListener) listenerRoutine() { p.Close() p = nil err = <-ch - if err == nil { - err = FileClosed + if err == nil || err == ErrFileClosed { + err = ErrPipeListenerClosed } closed = true } @@ -242,7 +248,7 @@ func (l *win32PipeListener) Accept() (net.Conn, error) { response := <-ch return response.p, response.err case <-l.doneCh: - return nil, FileClosed + return nil, ErrPipeListenerClosed } } diff --git a/pipe_test.go b/pipe_test.go index 2e4df70..433588c 100644 --- a/pipe_test.go +++ b/pipe_test.go @@ -26,8 +26,8 @@ func TestDialListenerTimesOut(t *testing.T) { defer l.Close() var d = time.Duration(10 * time.Millisecond) _, err = DialPipe(testPipeName, &d) - if err != syscall.ETIMEDOUT { - t.Fatalf("expected ETIMEDOUT, got %v", err) + if err != ErrTimeout { + t.Fatalf("expected ErrTimeout, got %v", err) } } @@ -88,8 +88,8 @@ func TestReadTimeout(t *testing.T) { buf := make([]byte, 10) _, err = c.Read(buf) - if err != syscall.ETIMEDOUT { - t.Fatalf("expected ETIMEDOUT, got %v", err) + if err != ErrTimeout { + t.Fatalf("expected ErrTimeout, got %v", err) } } @@ -159,19 +159,19 @@ func TestCloseAbortsListen(t *testing.T) { t.Fatal(err) } - ch := make(chan int) + ch := make(chan error) go func() { _, err := l.Accept() - if err != syscall.ERROR_OPERATION_ABORTED { - t.Fatalf("expected ERROR_OPERATION_ABORTED, got %v", err) - } - ch <- 1 + ch <- err }() time.Sleep(30 * time.Millisecond) l.Close() - <-ch + err = <-ch + if err != ErrPipeListenerClosed { + t.Fatalf("expected ErrPipeListenerClosed, got %v", err) + } } func TestAcceptAfterCloseFails(t *testing.T) { @@ -181,7 +181,7 @@ func TestAcceptAfterCloseFails(t *testing.T) { } l.Close() _, err = l.Accept() - if err != FileClosed { - t.Fatalf("expected FileClosed, got %v", err) + if err != ErrPipeListenerClosed { + t.Fatalf("expected ErrPipeListenerClosed, got %v", err) } }