import "errors"
-// ErrClosing is returned when a descriptor is used after it has been closed.
-var ErrClosing = errors.New("use of closed file or network connection")
+// ErrNetClosing is returned when a network descriptor is used after
+// it has been closed. Keep this string consistent because of issue
+// #4373: since historically programs have not been able to detect
+// this error, they look for the string.
+var ErrNetClosing = errors.New("use of closed network connection")
+
+// ErrFileClosing is returned when a file descriptor is used after it
+// has been closed.
+var ErrFileClosing = errors.New("use of closed file")
+
+// Return the appropriate closing error based on isFile.
+func errClosing(isFile bool) error {
+ if isFile {
+ return ErrFileClosing
+ }
+ return ErrNetClosing
+}
// ErrTimeout is returned for an expired deadline.
var ErrTimeout error = &TimeoutError{}
// It returns an error when fd cannot be used.
func (fd *FD) incref() error {
if !fd.fdmu.incref() {
- return ErrClosing
+ return errClosing(fd.isFile)
}
return nil
}
// It returns an error when fd cannot be used for reading.
func (fd *FD) readLock() error {
if !fd.fdmu.rwlock(true) {
- return ErrClosing
+ return errClosing(fd.isFile)
}
return nil
}
// It returns an error when fd cannot be used for writing.
func (fd *FD) writeLock() error {
if !fd.fdmu.rwlock(false) {
- return ErrClosing
+ return errClosing(fd.isFile)
}
return nil
}
wtimer *time.Timer
rtimedout atomicBool // set true when read deadline has been reached
wtimedout atomicBool // set true when write deadline has been reached
+
+ // Whether this is a normal file.
+ // On Plan 9 we do not use this package for ordinary files,
+ // so this is always false, but the field is present because
+ // shared code in fd_mutex.go checks it.
+ isFile bool
}
// We need this to close out a file descriptor when it is unlocked,
// is in the net package.
func (fd *FD) Close() error {
if !fd.fdmu.increfAndClose() {
- return ErrClosing
+ return errClosing(fd.isFile)
}
return nil
}
}
}
-func (pd *pollDesc) prepare(mode int) error {
+func (pd *pollDesc) prepare(mode int, isFile bool) error {
if pd.closing {
- return ErrClosing
+ return errClosing(isFile)
}
return nil
}
-func (pd *pollDesc) prepareRead() error { return pd.prepare('r') }
+func (pd *pollDesc) prepareRead(isFile bool) error { return pd.prepare('r', isFile) }
-func (pd *pollDesc) prepareWrite() error { return pd.prepare('w') }
+func (pd *pollDesc) prepareWrite(isFile bool) error { return pd.prepare('w', isFile) }
-func (pd *pollDesc) wait(mode int) error {
+func (pd *pollDesc) wait(mode int, isFile bool) error {
if pd.closing {
- return ErrClosing
+ return errClosing(isFile)
}
return ErrTimeout
}
-func (pd *pollDesc) waitRead() error { return pd.wait('r') }
+func (pd *pollDesc) waitRead(isFile bool) error { return pd.wait('r', isFile) }
-func (pd *pollDesc) waitWrite() error { return pd.wait('w') }
+func (pd *pollDesc) waitWrite(isFile bool) error { return pd.wait('w', isFile) }
func (pd *pollDesc) waitCanceled(mode int) {}
runtime_pollUnblock(pd.runtimeCtx)
}
-func (pd *pollDesc) prepare(mode int) error {
+func (pd *pollDesc) prepare(mode int, isFile bool) error {
if pd.runtimeCtx == 0 {
return nil
}
res := runtime_pollReset(pd.runtimeCtx, mode)
- return convertErr(res)
+ return convertErr(res, isFile)
}
-func (pd *pollDesc) prepareRead() error {
- return pd.prepare('r')
+func (pd *pollDesc) prepareRead(isFile bool) error {
+ return pd.prepare('r', isFile)
}
-func (pd *pollDesc) prepareWrite() error {
- return pd.prepare('w')
+func (pd *pollDesc) prepareWrite(isFile bool) error {
+ return pd.prepare('w', isFile)
}
-func (pd *pollDesc) wait(mode int) error {
+func (pd *pollDesc) wait(mode int, isFile bool) error {
if pd.runtimeCtx == 0 {
return errors.New("waiting for unsupported file type")
}
res := runtime_pollWait(pd.runtimeCtx, mode)
- return convertErr(res)
+ return convertErr(res, isFile)
}
-func (pd *pollDesc) waitRead() error {
- return pd.wait('r')
+func (pd *pollDesc) waitRead(isFile bool) error {
+ return pd.wait('r', isFile)
}
-func (pd *pollDesc) waitWrite() error {
- return pd.wait('w')
+func (pd *pollDesc) waitWrite(isFile bool) error {
+ return pd.wait('w', isFile)
}
func (pd *pollDesc) waitCanceled(mode int) {
runtime_pollWaitCanceled(pd.runtimeCtx, mode)
}
-func convertErr(res int) error {
+func convertErr(res int, isFile bool) error {
switch res {
case 0:
return nil
case 1:
- return ErrClosing
+ return errClosing(isFile)
case 2:
return ErrTimeout
}
}{
{100, nil, &FD{ZeroReadIsEOF: true}, nil},
{100, io.EOF, &FD{ZeroReadIsEOF: true}, io.EOF},
- {100, ErrClosing, &FD{ZeroReadIsEOF: true}, ErrClosing},
+ {100, ErrNetClosing, &FD{ZeroReadIsEOF: true}, ErrNetClosing},
{0, nil, &FD{ZeroReadIsEOF: true}, io.EOF},
{0, io.EOF, &FD{ZeroReadIsEOF: true}, io.EOF},
- {0, ErrClosing, &FD{ZeroReadIsEOF: true}, ErrClosing},
+ {0, ErrNetClosing, &FD{ZeroReadIsEOF: true}, ErrNetClosing},
{100, nil, &FD{ZeroReadIsEOF: false}, nil},
{100, io.EOF, &FD{ZeroReadIsEOF: false}, io.EOF},
- {100, ErrClosing, &FD{ZeroReadIsEOF: false}, ErrClosing},
+ {100, ErrNetClosing, &FD{ZeroReadIsEOF: false}, ErrNetClosing},
{0, nil, &FD{ZeroReadIsEOF: false}, nil},
{0, io.EOF, &FD{ZeroReadIsEOF: false}, io.EOF},
- {0, ErrClosing, &FD{ZeroReadIsEOF: false}, ErrClosing},
+ {0, ErrNetClosing, &FD{ZeroReadIsEOF: false}, ErrNetClosing},
}
func TestEOFError(t *testing.T) {
// Whether a zero byte read indicates EOF. This is false for a
// message based socket connection.
ZeroReadIsEOF bool
+
+ // Whether this is a file rather than a network socket.
+ isFile bool
}
// Init initializes the FD. The Sysfd field should already be set.
// This can be called multiple times on a single FD.
-func (fd *FD) Init() error {
+// The net argument is a network name from the net package (e.g., "tcp"),
+// or "file".
+func (fd *FD) Init(net string, pollable bool) error {
+ // We don't actually care about the various network types.
+ if net == "file" {
+ fd.isFile = true
+ }
+ if !pollable {
+ return nil
+ }
return fd.pd.init(fd)
}
// destroy method when there are no remaining references.
func (fd *FD) Close() error {
if !fd.fdmu.increfAndClose() {
- return ErrClosing
+ return errClosing(fd.isFile)
}
// Unblock any I/O. Once it all unblocks and returns,
// so that it cannot be referring to fd.sysfd anymore,
// the final decref will close fd.sysfd. This should happen
// fairly quickly, since all the I/O is non-blocking, and any
- // attempts to block in the pollDesc will return ErrClosing.
+ // attempts to block in the pollDesc will return errClosing(fd.isFile).
fd.pd.evict()
// The call to decref will call destroy if there are no other
// references.
// TODO(bradfitz): make it wait for readability? (Issue 15735)
return 0, nil
}
- if err := fd.pd.prepareRead(); err != nil {
+ if err := fd.pd.prepareRead(fd.isFile); err != nil {
return 0, err
}
if fd.IsStream && len(p) > maxRW {
if err != nil {
n = 0
if err == syscall.EAGAIN {
- if err = fd.pd.waitRead(); err == nil {
+ if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
}
return 0, nil, err
}
defer fd.readUnlock()
- if err := fd.pd.prepareRead(); err != nil {
+ if err := fd.pd.prepareRead(fd.isFile); err != nil {
return 0, nil, err
}
for {
if err != nil {
n = 0
if err == syscall.EAGAIN {
- if err = fd.pd.waitRead(); err == nil {
+ if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
}
return 0, 0, 0, nil, err
}
defer fd.readUnlock()
- if err := fd.pd.prepareRead(); err != nil {
+ if err := fd.pd.prepareRead(fd.isFile); err != nil {
return 0, 0, 0, nil, err
}
for {
if err != nil {
// TODO(dfc) should n and oobn be set to 0
if err == syscall.EAGAIN {
- if err = fd.pd.waitRead(); err == nil {
+ if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
}
return 0, err
}
defer fd.writeUnlock()
- if err := fd.pd.prepareWrite(); err != nil {
+ if err := fd.pd.prepareWrite(fd.isFile); err != nil {
return 0, err
}
var nn int
return nn, err
}
if err == syscall.EAGAIN {
- if err = fd.pd.waitWrite(); err == nil {
+ if err = fd.pd.waitWrite(fd.isFile); err == nil {
continue
}
}
return 0, err
}
defer fd.writeUnlock()
- if err := fd.pd.prepareWrite(); err != nil {
+ if err := fd.pd.prepareWrite(fd.isFile); err != nil {
return 0, err
}
for {
err := syscall.Sendto(fd.Sysfd, p, 0, sa)
if err == syscall.EAGAIN {
- if err = fd.pd.waitWrite(); err == nil {
+ if err = fd.pd.waitWrite(fd.isFile); err == nil {
continue
}
}
return 0, 0, err
}
defer fd.writeUnlock()
- if err := fd.pd.prepareWrite(); err != nil {
+ if err := fd.pd.prepareWrite(fd.isFile); err != nil {
return 0, 0, err
}
for {
n, err := syscall.SendmsgN(fd.Sysfd, p, oob, sa, 0)
if err == syscall.EAGAIN {
- if err = fd.pd.waitWrite(); err == nil {
+ if err = fd.pd.waitWrite(fd.isFile); err == nil {
continue
}
}
}
defer fd.readUnlock()
- if err := fd.pd.prepareRead(); err != nil {
+ if err := fd.pd.prepareRead(fd.isFile); err != nil {
return -1, nil, "", err
}
for {
}
switch err {
case syscall.EAGAIN:
- if err = fd.pd.waitRead(); err == nil {
+ if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
case syscall.ECONNABORTED:
if err != nil {
n = 0
if err == syscall.EAGAIN {
- if err = fd.pd.waitRead(); err == nil {
+ if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
}
// WaitWrite waits until data can be read from fd.
func (fd *FD) WaitWrite() error {
- return fd.pd.waitWrite()
+ return fd.pd.waitWrite(fd.isFile)
}
fd := o.fd
// Notify runtime netpoll about starting IO.
- err := fd.pd.prepare(int(o.mode))
+ err := fd.pd.prepare(int(o.mode), fd.isFile)
if err != nil {
return 0, err
}
return 0, err
}
// Wait for our request to complete.
- err = fd.pd.wait(int(o.mode))
+ err = fd.pd.wait(int(o.mode), fd.isFile)
if err == nil {
// All is good. Extract our IO results and return.
if o.errno != 0 {
// IO is interrupted by "close" or "timeout"
netpollErr := err
switch netpollErr {
- case ErrClosing, ErrTimeout:
+ case ErrNetClosing, ErrFileClosing, ErrTimeout:
// will deal with those.
default:
panic("unexpected runtime.netpoll error: " + netpollErr.Error())
// the destroy method when there are no remaining references.
func (fd *FD) Close() error {
if !fd.fdmu.increfAndClose() {
- return ErrClosing
+ return errClosing(fd.isFile)
}
// unblock pending reader and writer
fd.pd.evict()
break
}
if err1 == syscall.EAGAIN {
- if err1 = dstFD.pd.waitWrite(); err1 == nil {
+ if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
continue
}
}
break
}
if err1 == syscall.EAGAIN {
- if err1 = dstFD.pd.waitWrite(); err1 == nil {
+ if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
continue
}
}
break
}
if err1 == syscall.EAGAIN {
- if err1 = dstFD.pd.waitWrite(); err1 == nil {
+ if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
continue
}
}
return 0, err
}
defer fd.writeUnlock()
- if err := fd.pd.prepareWrite(); err != nil {
+ if err := fd.pd.prepareWrite(fd.isFile); err != nil {
return 0, err
}
n += int64(wrote)
consume(v, int64(wrote))
if e0 == syscall.EAGAIN {
- if err = fd.pd.waitWrite(); err == nil {
+ if err = fd.pd.waitWrite(fd.isFile); err == nil {
continue
}
} else if e0 != 0 {
"net/internal/socktest"
"os"
"runtime"
+ "strings"
"testing"
"time"
)
goto third
}
switch nestedErr {
- case errCanceled, poll.ErrClosing, errMissingAddress, errNoSuitableAddress,
+ case errCanceled, poll.ErrNetClosing, errMissingAddress, errNoSuitableAddress,
context.DeadlineExceeded, context.Canceled:
return nil
}
goto third
}
switch nestedErr {
- case poll.ErrClosing, poll.ErrTimeout:
+ case poll.ErrNetClosing, poll.ErrTimeout:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
goto third
}
switch nestedErr {
- case errCanceled, poll.ErrClosing, errMissingAddress, poll.ErrTimeout, ErrWriteToConnected, io.ErrUnexpectedEOF:
+ case errCanceled, poll.ErrNetClosing, errMissingAddress, poll.ErrTimeout, ErrWriteToConnected, io.ErrUnexpectedEOF:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
// parseCloseError parses nestedErr and reports whether it is a valid
// error value from Close functions.
// It returns nil when nestedErr is valid.
-func parseCloseError(nestedErr error) error {
+func parseCloseError(nestedErr error, isShutdown bool) error {
if nestedErr == nil {
return nil
}
+ // Because historically we have not exported the error that we
+ // return for an operation on a closed network connection,
+ // there are programs that test for the exact error string.
+ // Verify that string here so that we don't break those
+ // programs unexpectedly. See issues #4373 and #19252.
+ want := "use of closed network connection"
+ if !isShutdown && !strings.Contains(nestedErr.Error(), want) {
+ return fmt.Errorf("error string %q does not contain expected string %q", nestedErr, want)
+ }
+
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
goto third
}
switch nestedErr {
- case poll.ErrClosing:
+ case poll.ErrNetClosing:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
for i := 0; i < 3; i++ {
err = c.(*TCPConn).CloseRead()
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, true); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
for i := 0; i < 3; i++ {
err = c.(*TCPConn).CloseWrite()
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, true); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
for i := 0; i < 3; i++ {
err = c.Close()
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
err = ln.Close()
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
for i := 0; i < 3; i++ {
err = pc.Close()
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
goto third
}
switch nestedErr {
- case poll.ErrClosing, poll.ErrTimeout:
+ case poll.ErrNetClosing, poll.ErrTimeout:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
goto third
}
switch nestedErr {
- case poll.ErrClosing:
+ case poll.ErrNetClosing:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
}
func (fd *netFD) init() error {
- return fd.pfd.Init()
+ return fd.pfd.Init(fd.net, true)
}
func (fd *netFD) setAddr(laddr, raddr Addr) {
return mapErr(ctx.Err())
default:
}
- if err := fd.pfd.Init(); err != nil {
+ if err := fd.pfd.Init(fd.net, true); err != nil {
return err
}
runtime.KeepAlive(fd)
default:
return os.NewSyscallError("connect", err)
}
- if err := fd.pfd.Init(); err != nil {
+ if err := fd.pfd.Init(fd.net, true); err != nil {
return err
}
if deadline, _ := ctx.Deadline(); !deadline.IsZero() {
f, err = c1.File()
}
if err := c1.Close(); err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Error(perr)
}
t.Error(err)
f, err = c1.File()
}
if err := c1.Close(); err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Error(perr)
}
t.Error(err)
err = c.CloseRead()
}
if err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, true); perr != nil {
t.Error(perr)
}
t.Fatal(err)
err = c.CloseWrite()
}
if err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, true); perr != nil {
t.Error(perr)
}
t.Error(err)
err = c.CloseWrite()
}
if err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, true); perr != nil {
t.Error(perr)
}
t.Fatal(err)
defer c.Close()
if err := c.Close(); err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Error(perr)
}
t.Fatal(err)
dst := ln.Addr().String()
if err := ln.Close(); err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Error(perr)
}
t.Fatal(err)
defer c.Close()
if err := c.Close(); err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Error(perr)
}
t.Fatal(err)
}
addr := ln.Addr().String()
if err := ln.Close(); err != nil {
- if perr := parseCloseError(err); perr != nil {
+ if perr := parseCloseError(err, false); perr != nil {
t.Error(perr)
}
t.Fatal(err)
}
n, e := f.read(b)
if e != nil {
- if e == poll.ErrClosing {
+ if e == poll.ErrFileClosing {
e = ErrClosed
}
if e == io.EOF {
pollable = false
}
- if pollable {
- if err := f.pfd.Init(); err != nil {
- // An error here indicates a failure to register
- // with the netpoll system. That can happen for
- // a file descriptor that is not supported by
- // epoll/kqueue; for example, disk files on
- // GNU/Linux systems. We assume that any real error
- // will show up in later I/O.
- } else {
- // We successfully registered with netpoll, so put
- // the file into nonblocking mode.
- if err := syscall.SetNonblock(fdi, true); err == nil {
- f.nonblock = true
- }
+ if err := f.pfd.Init("file", pollable); err != nil {
+ // An error here indicates a failure to register
+ // with the netpoll system. That can happen for
+ // a file descriptor that is not supported by
+ // epoll/kqueue; for example, disk files on
+ // GNU/Linux systems. We assume that any real error
+ // will show up in later I/O.
+ } else if pollable {
+ // We successfully registered with netpoll, so put
+ // the file into nonblocking mode.
+ if err := syscall.SetNonblock(fdi, true); err == nil {
+ f.nonblock = true
}
}
t.Error("Read of closed pipe unexpectedly succeeded")
} else if pe, ok := err.(*os.PathError); !ok {
t.Errorf("Read of closed pipe returned unexpected error type %T; expected os.PathError", pe)
+ } else if pe.Err != os.ErrClosed {
+ t.Errorf("got error %q but expected %q", pe.Err, os.ErrClosed)
} else {
t.Logf("Read returned expected error %q", err)
}