Should net.TCPConn.Read() be returning an EOF on err in this case?

3,133 views
Skip to first unread message

Tim Henderson

unread,
Nov 3, 2012, 4:15:13 PM11/3/12
to golan...@googlegroups.com
Go Hackers,

Sometimes when calling `TCPConn.Read([]byte)(n int, err error)` you get the following error:

"read ip:port use of closed network connection"

This seems like an EOF and I want to treat it as one. It happens via this chain of events

http://golang.org/src/pkg/net/tcpsock_posix.go?s=1875:1926#L73
    82	// Read implements the Conn Read method.
    83	func (c *TCPConn) Read(b []byte) (n int, err error) {
    84		if !c.ok() {
    85			return 0, syscall.EINVAL
    86		}
    87		return c.fd.Read(b)
    88	}

http://golang.org/src/pkg/net/fd.go
   416	func (fd *netFD) Read(p []byte) (n int, err error) {
   417		fd.rio.Lock()
   418		defer fd.rio.Unlock()
   419		if err := fd.incref(false); err != nil {
   420			return 0, err
   421		}
   422		defer fd.decref()
   423		for {
   424			n, err = syscall.Read(int(fd.sysfd), p)
   425			if err == syscall.EAGAIN {
   426				err = errTimeout
   427				if fd.rdeadline >= 0 {
   428					if err = pollserver.WaitRead(fd); err == nil {
   429						continue
   430					}
   431				}
   432			}
   433			if err != nil {
   434				n = 0
   435			} else if n == 0 && err == nil && fd.sotype != syscall.SOCK_DGRAM {
   436				err = io.EOF
   437			}
   438			break
   439		}
   440		if err != nil && err != io.EOF {
   441			err = &OpError{"read", fd.net, fd.raddr, err}
   442		}
   443		return
   444	}

http://golang.org/src/pkg/net/fd.go
   342	var errClosing = errors.New("use of closed network connection")
   343	
   344	// Add a reference to this fd.
   345	// If closing==true, pollserver must be locked; mark the fd as closing.
   346	// Returns an error if the fd cannot be used.
   347	func (fd *netFD) incref(closing bool) error {
   348		if fd == nil {
   349			return errClosing
   350		}
   351		fd.sysmu.Lock()
   352		if fd.closing {
   353			fd.sysmu.Unlock()
   354			return errClosing
   355		}
   356		fd.sysref++
   357		if closing {
   358			fd.closing = true
   359		}
   360		fd.sysmu.Unlock()
   361		return nil
   362	}

It seems like errClosing is actually an EOF but maybe this is incorrect? Should you treat it as an EOF or has something different happened? Should these errors be differentiated in anyway and if not should errClosing be changed to an io.EOF in (*netFD).incref()?

-Tim

brainman

unread,
Nov 3, 2012, 11:45:10 PM11/3/12
to golan...@googlegroups.com
On Sunday, 4 November 2012 07:15:13 UTC+11, Tim Henderson wrote:
...
This seems like an EOF and I want to treat it as one. ...

 
I am not entirely convinced, but, perhaps, you have a point. It would happen, if you are reading from a connection on one goroutine, while you issue Close on the same connection on another goroutine. I think it is similar to issuing CloseWrite, where you would get EOF from os. Feel free to create an issue http://code.google.com/p/go/issues/list, if you convinced enough. Or, perhaps, others will comment here.

Alex

Kevin Gillette

unread,
Nov 4, 2012, 9:55:26 AM11/4/12
to golan...@googlegroups.com
If the remote side closed the connection, would it produce an EOF or this same error? Also, if that hypothetical other goroutine closed the connection, would any buffered yet unread data in the stream be read before that error was issued?

brainman

unread,
Nov 4, 2012, 5:25:49 PM11/4/12
to golan...@googlegroups.com
On Monday, 5 November 2012 01:55:26 UTC+11, Kevin Gillette wrote:

> If the remote side closed the connection, would it produce an EOF or this same error? ...

If you have both your peer and another goroutine closing connection, then it is a race and anything is possible. You could get either.


> Also, if that hypothetical other goroutine closed the connection, would any buffered yet unread data in the stream be read before that error was issued?

Again, it is matter of chance. net.errClosing is checked for at the start of your read, so it will keep returning your buffered network data until it discovers net.errClosing. There is no guarantee that all your buffered data is read.

Alex

Tim Henderson

unread,
Nov 4, 2012, 8:16:26 PM11/4/12
to golan...@googlegroups.com
Well, I don't know what the right answer is but I am currently using this function in my code:


/*
A utility function which tests if an error returned from a TCPConnection or
TCPListener is actually an EOF. In some edge cases this which should be treated
as EOFs are not returned as one.
*/
func IsEOF(err error) bool {
    if err == nil {
        return false
    } else if err == io.EOF {
        return true
    } else if oerr, ok := err.(*net.OpError); ok {
        /* this hack happens because the error is returned when the
         * network socket is closing and instead of returning a
         * io.EOF it returns this error.New(...) struct. */
        if oerr.Err.Error() == "use of closed network connection" {
            return true
        }
    } else {
        if err.Error() == "use of closed network connection" {
            return true
        }
    }
    return false
}

I am treating it as an EOF because I can't think of any other way to handle the error. It could be there is an edge case which distinguishes the error in some important way so I don't think I will open an issue. However, if some else has more insight and feels and EOF is the right answer feel free to do so.

-Tim
Reply all
Reply to author
Forward
0 new messages