1

I wish to confirm there are no more bytes to be read from a buffered reader (neither from the internal buffer, nor from the underlying file object) by trying to read one more byte (and catching EOF).

Is using bufio.Read or bufio.ReadByte suitable for this purpose?

It's not clear from the bufio.Read documentation whether or not the integer returned can be zero, in non-EOF cases. Namely, is 0, nil a valid return value if len(p) > 0?

func (b *Reader) Read(p []byte) (n int, err error)

Read reads data into p. It returns the number of bytes read into p. The bytes are taken from at most one Read on the underlying Reader, hence n may be less than len(p). To read exactly len(p) bytes, use io.ReadFull(b, p). At EOF, the count will be zero and err will be io.EOF.

Similarly, the bufio.ReadByte documentation doesn't separate error cases from EOF cases very well, and it doesn't exactly define what it means by "available" (i.e. available in the internal buffer, or available in the underlying file)?

func (b *Reader) ReadByte() (byte, error)

ReadByte reads and returns a single byte. If no byte is available, returns an error.

Community
  • 1
  • 1
init_js
  • 4,143
  • 2
  • 23
  • 53

1 Answers1

3

Passing a buffer of length 1 to bufio.Read, when the reader is backed with an underlying os.File, will indeed return n==0, io.EOF if the file is at EOF.

The documentation is being a bit imprecise because some of the behavior depends on the underlying reader you pass to the bufio reader. The code for bufio.Read() draws a more accurate picture. I'll outline the logic.

bufio.Read: Only issues a Read to the underlying reader if all bytes in the internal buffer have been exhausted. So, presumably, if you've already read as many bytes from the buffered reader as the number of bytes in the underlying file, that internal buffer should be exhausted when you make the last call bufio.Read(buf[0:1]) to check for EOF.

When the internal buffer is exhausted, and you ask the bufio reader for more, bufio.Read will do at most one call to the underlying reader. The type of error you get then will depend on your underlying reader.

Asking to read for n > 0 bytes from an os.File when the read pointer is already at EOF should return 0, io.EOF (according to the doc on os.File File.Read). But if your underlying reader was something else, perhaps a custom type specific to your application designed to return 0, nil at EOF, then bufio.Read would echo that back instead.

bufio.ReadByte: The logic behind bufio.ReadByte is slightly different but the outcome should be the same as bufio.Read in cases where the underlying reader is an os.File. The main difference with bufio.Read is that bufio.ReadByte can make several attempts to refill the internal buffer. If an error is encountered during refilling (which will be the case for a os.File reader at EOF), it is returned after the first erroneous read attempt. So, if your underlying Reader is an os.File reader, then you'll get 0, io.EOF if and only if your underlying file is at EOF. If your underlying reader was a custom reader type that only returned 0, nil at EOF, then bufio.ReadByte would eventually emit a "NoProgress" error. I'm not sure why the retry logic is only in bufio.ReadByte, but the good news is that either option can be used if your underlying file behaves like an os.File.

Other info:

This is not directly applicable to golang, but you may find the following thread interesting: Can read(2) return zero bytes when not at EOF. Its topic is the read() system call's semantics (POSIX). Reads on non-blocking sockets/files, even when no data is ready, should return -1, not 0, and set errno EAGAIN (or EINTR when interrupted). Non-blocking sockets/files are not really a concept native to go (as far as i know), and the bufio module in particular will panic() whenever/if the underlying reader returns negative numbers, so you don't have to worry about it.

init_js
  • 4,143
  • 2
  • 23
  • 53