-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bytes: panic from Read in (*Buffer).ReadFrom fills with 512 zero bytes #25435
Comments
Change https://golang.org/cl/113495 mentions this issue: |
You have a data race in your example. |
While true, the race doesn't have any impact on the proof (it's after the proof, in the final Println). |
A version without channels: https://play.golang.org/p/juW8Q47XRLA The fix #25436 should be valid. |
A fix can only be valid if something is broken. https://play.golang.org/p/Fmmki6ZLI_K
A bytes buffer is not safe for concurrent use. Accessing it within
That being said, the documentation clearly states that the buffer may resize itself as needed. |
I don't understand why this is a bug. Don't examine a |
I believe it's a bug because there was a change in behaviour between go1.9 and go1.10. @as - The example provided is a contrived proof. Yes, the no-op re-slice in the example Read() is inconsequential to the Buffer.buf slice, and the re-slice in ReadFrom (after Read()) is the key to the final state of the buffer. The original issue was discovered working with an ssh client (based on golang.org/x/crypto/ssh). The client passes a *bytes.Buffer as the io.Writer in Session.Stdout and Session.Stderr. The ssh package then starts io.Copy in a goroutine to write the stdout/err streams into the provided io.Writer (the *bytes.Buffer) here: https://github.com/golang/crypto/blob/master/ssh/session.go#L524. We have wrapped the ssh.Run() call in a function with a context.WithTimeout and examine the buffers either when the ssh.Run returns, or the context times out. In all such timeout cases, the Read() hasn't put anything in the buffer, as it is locked waiting for data from the ssh server. The fix proposed in #25436 simply keeps the buffer empty after it is "grown", using the same logic as in the exported Grow() method: https://github.com/golang/go/blob/master/src/bytes/buffer.go#L159 |
The mere fact of a behavior change between 1.9 and 1.10 doesn't necessarily indicate a bug. We are allowed to change behavior that the caller is not expected to observe. Looking only at your description, it sounds like there is a data race in the code you are describing. After the context has timed out, but before the A more interesting question is: why do you want to do this? |
Thanks @ianlancetaylor, some background info on the "why":
As such, after the session is closed, i thought it reasonable to inspect the Buffer. It does still seem to be racy, as I can find the Buffer.buf is truncated again (by ReadFrom) if i sleep for as low as 150us after the Close() call (ew!). The test I wrote in my patch does not trigger the race detector and manages concurrent access to the Buffer with the explicit control channel between the Reader and the main goroutine. So while a raw bytes.Buffer is not guaranteed to be safe for concurrent access, the control channel makes it "safe enough" in this case (unless I'm wrong?). tl;dr: After timing out and |
If a reference to this buffer has passed to a goroutine which has not yet stopped, this is not safe. |
The current package main
import (
"fmt"
"bytes"
"io"
)
type T struct{}
func (T) Read(p []byte) (n int, err error) {
panic(nil)
return 0, io.EOF
}
func main() {
var b bytes.Buffer
defer func() {
recover()
fmt.Println(b.Len(), b.Cap()) // 512 512
}()
fmt.Println(b.Len(), b.Cap()) // 0 0
b.ReadFrom(T{})
} |
Can you please cite your source for this assertion. |
At best this is an implementation detail. More realistically these values are undefined in the presence of a panic. |
Yes, this may be not a bug. But op's PR is really an improvement. |
See my last example please. Yes, if that unexpected result is allowed, the |
I'm willing to consider the change if someone writes a test case showing the behavior of |
@forfuncsake you can use my last example in your test case: https://go-review.googlesource.com/c/go/+/113495/2/src/bytes/buffer_test.go |
Thanks all - I've pushed a new commit with TestReadFromPanicReader to replace the other test. |
@ianlancetaylor, I confirm that the test shows the behavior change from 1.9 to 1.10. Will approve and submit. |
Please answer these questions before submitting your issue. Thanks!
What did you do?
I discovered this bug when testing an ssh client on go1.10. The client used a context.WithTimeout() to hang up and return after a defined timeout period. When checking the value of the *Buffer passed in to session.Stderr, I was presented with a 512-length slice of zero-value bytes any time the context timed out.
This bug only presents in go 1.10 and above.
I managed to recreate the issue directly with Buffer.ReadFrom(), the call to b.grow() causes the buffer to use a new slice of bytes with a length of 512 (MinRead), which is subsequently re-managed by the Reader.
Until the Reader completes its Read, the previously empty buffer remains "full":
https://play.golang.org/p/W-VEKbdsOfF
What did you expect to see?
An empty Buffer
What did you see instead?
A buffer filled with 512 zero-value bytes
System details
The text was updated successfully, but these errors were encountered: