go io: ReadFull不应该将io.ErrUnexpectedEOF从底层读取器转发

6jygbczu  于 6个月前  发布在  Go
关注(0)|答案(4)|浏览(58)

io.ReadFull 的逻辑会从底层的 io.Reader 中直接返回错误。
io.Reader 返回 io.ErrUnexpectedEOF 的情况下,调用者无法区分以下两种情况:

  • 底层的 io.Reader 突然结束并处于截断状态,或者
  • 底层的 io.Reader 正确结束且没有更多数据。

为了避免这种歧义,我们应该 Package 底层的任何 io.ErrUnexpectedEOF
然而,我怀疑这是一种破坏性更改,我们能做的最好的事情就是记录这一点。
\cc @josharian@catzkorn

xpcnnkqh

xpcnnkqh1#

CC @ianlancetaylor, @rsc。

hwazgwia

hwazgwia2#

我同意我们不能在这一点上改变这个:
底层的读取错误永远不会被 Package 在这些实用函数中。
我很好奇你是如何使用io.ReadFull的,这会有什么影响。
ReadFull(n)的目的是说:“我确定有n个字节要被读取,我想读取它们。”
如果底层的读取器只有,比如说,n-1个字节,
它并不真正关心底层的读取器在这些n-1个字节之后认为是否有一个“好的EOF”或“坏的EOF”。
无论如何,ReadFull知道这是一个坏的EOF。
从另一个Angular 来看,实际上只期望ReadFull、ReadAtLeast等返回ErrUnexpectedEOF。
一般的读取器实现可能不应该从普通的Read中返回一个普通的ErrUnexpectedEOF。
相反,它应该返回一个更详细的错误。
所以问题可能出在你正在使用的特定Reader上:
它不应该在一开始就从Read中返回一个裸露的ErrUnexpectedEOF。

sg3maiej

sg3maiej3#

我好奇你是如何使用io.ReadFull的,这很重要。
我们有一个(池化的)缓冲区来读取,以及一个未知大小的数据源。我们想尽可能多地将数据读入缓冲区,直到缓冲区满(哎呀,拿一个大一点的缓冲区)或者我们到达EOF。
我认为这个问题中的代码之所以使用ReadFull,是因为它是io包中最明显的方便函数,可以让你使用缓冲区。而且它在这个问题上运行得很好。
修复方法是编写自己的自定义读取循环。这可能是正确的答案,但发现ReadFull中有一个隐藏的陷阱,因此出现了这个问题。这可能是io包的一个不错的补充。(Read的语义意味着对于所有使用io.Reader的情况,都需要read帮助器。)
所以问题可能出在你正在使用的特定Reader上:
它不应该在一开始就从Read中返回裸露的ErrUnexpectedEOF。
激发此问题的案例是底层的reader,即net/http.Request.Body。如果在读取过程中连接关闭,它会返回ErrUnexpectedEOF。我们也无法改变这一点。

sshcrbum

sshcrbum4#

@josharian描述的代码看起来像这样:

var buf []byte
switch n1, err := io.ReadFull(r, smallBuf); err {
case io.EOF, io.ErrUnexpectedEOF:
	buf = smallBuf[:n]
case nil:
	switch n2, err := io.ReadFull(r, bigBuf[n1:]); err {
	case io.EOF, io.ErrUnexpectedEOF:
		copy(bigBuf[:n1], smallBuf[:n1])
		buf = bigBuf[:n1+n2]
	case nil:
		return errors.New("input too large")
	default:
		return err
	}
default:
	return err
}

这段代码真的很奇怪,因为错误条件完全颠倒了(即,nil是错误,而 io.EOFio.ErrUnexpectedEOF 是成功)。我们有一个截断错误,如果底层读取器返回 io.ErrUnexpectedEOF ,那么我们将其视为成功(糟糕)。
我们最终编写了自己的 io.ReadFull 类似功能的反向语义:

// ReadUntilEOF continually reads r into b until io.EOF.
// It returns nil if io.EOF is encountered.
// It returns io.ErrShortBuffer if the entirety of the buffer is filled,
// which indicates that a larger buffer is needed.
func ReadUntilEOF(r io.Reader, b []byte) (n int, err error) {
	for n < len(b) && err == nil {
		var nn int
		nn, err = r.Read(b[n:])
		n += nn
	}
	switch {
	case err == io.EOF:
		return n, nil
	case n == len(buf):
		return n, io.ErrShortBuffer
	default:
		return n, err
	}
}

使用 ReadUntilEOF ,我们之前的代码片段变得更加易读:

var buf []byte
switch n1, err := ReadUntilEOF(r, smallBuf); err {
case nil:
	buf = smallBuf[:n]
case io.ErrShortBuffer:
	switch n2, err := io.ReadFull(bigBuf[n1:]); err {
	case nil:
		copy(bigBuf[:n1], smallBuf[:n1])
		buf = bigBuf[:n1+n2]
	case io.ErrShortBuffer:
		return errors.New("input too large")
	default:
		return err
	}
default:
	return err
}

我怀疑我们需要将尽可能多的 r 读入缓冲区的用例是独一无二的。我想知道是否有一个合理的用例,需要一个具有反向语义的 io.ReadFull 函数(即, io.ReadUntilEOF )。

相关问题