Repository navigation
fix(reader): stop TimeoutReader writing to p after timing out - #847
Open
dwisiswant0 wants to merge 1 commit into
Open
dwisiswant0 wants to merge 1 commit into
dwisiswant0 wants to merge 1 commit into
Conversation
TimeoutReader.Read handed the caller's buffer to a helper goroutine and returned ErrTimeout without waiting for it. When the underlying Read finished later, the goroutine wrote into p, which the caller may have reused by then, and into the named results n and err, a data race. It then blocked forever sending on an unbuffered channel nobody received from, leaking the goroutine. With a zero Timeout the context was never created and ctx.Done() dereferenced a nil interface, although the original implementation documented zero as "behaves like a normal reader". io.Reader cannot cancel a blocked Read, so the helper still runs until the source returns. Read now gives the source a private scratch buffer, copies into p only when the result arrives before the timer fires, and passes the result over a buffered channel so the helper exits as soon as the source returns. A Timeout of zero or less reads directly from the source. The context and the per-value datachan field are gone; the unexported field was never shared across calls because Read has a value receiver. Bytes delivered after a timeout are dropped, since a value receiver and exported literal construction leave nowhere to keep them; the doc comment says so. The scratch buffer costs an allocation and a copy per Read, each as long as the caller's buffer, which is the price of never sharing that buffer with the helper. Fixes: #837 Signed-off-by: Dwi Siswanto <git@dw1.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TimeoutReader.Read handed the caller's buffer to a helper goroutine and
returned ErrTimeout without waiting for it. When the underlying Read
finished later, the goroutine wrote into p, which the caller may have
reused by then, and into the named results n and err, a data race. It
then blocked forever sending on an unbuffered channel nobody received
from, leaking the goroutine. With a zero Timeout the context was never
created and ctx.Done() dereferenced a nil interface, although the
original implementation documented zero as "behaves like a normal
reader".
io.Reader cannot cancel a blocked Read, so the helper still runs until
the source returns. Read now gives the source a private scratch buffer,
copies into p only when the result arrives before the timer fires, and
passes the result over a buffered channel so the helper exits as soon as
the source returns. A Timeout of zero or less reads directly from the
source. The context and the per-value datachan field are gone; the
unexported field was never shared across calls because Read has a value
receiver.
Bytes delivered after a timeout are dropped, since a value receiver and
exported literal construction leave nowhere to keep them; the doc
comment says so. The scratch buffer costs an allocation and a copy per
Read, each as long as the caller's buffer, which is the price of never
sharing that buffer with the helper.
Fixes: #837