-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
net: Buffers makes multiple Write calls on Writers that don't implement buffersWriter #21676
Comments
This is an interesting one. I think it's somewhat clear that calling the But clearly for a low-level type it is desirable to minimize |
@ianlancetaylor hi! I think the problem is not in the |
@gobwas You are describing a general problem that Go code can run into, but as far as I can see this particular problem will not be helped by exporting the |
I understand the concern. I think that users of the API want to make the choice: not allocating might be important or not issuing more than one write might be important, depending on context. Exporting the interface would be a means of allowing the caller to use a type-assertion to determine capabilities of the Could we perhaps introduce a general I/O interface (consider this a sketch, nothing more): // Writever wraps the Writev method.
//
// Writev behaves identically to a Write where all of the byte slices in p are concatenated together.
type Writever interface {
Writev(p [][]byte) (n int, err error)
} Then in usage: func singleWritev(w io.Writer, p [][]byte) (n int, err error) {
if wv, ok := w.(Writever); ok {
return wv.Writev(p)
}
pp := bytes.Join(p, nil)
return w.Write(pp)
}
func noallocWritev(w io.Writer, p [][]byte) (n int, err error) {
if wv, ok := w.(Writever); ok {
return wv.Writev(p)
}
for _, pp := range p {
nn, err := w.Write(pp)
n += nn
if err != nil {
return n, err
}
}
return n, nil
} I could see this also being done as an alternate method on |
Wait, does anyone use Nagle's algorithm anymore? I thought we turned that off on all our file descriptors. |
@rsc It's off by default, but could be enabled by calling |
I think it's probably too late for this as an API change in Go 1.10. Let's leave this for Go 1.11 and be able to discuss with @bradfitz. I think maybe a more compelling motivation than a custom TCP wrapper would be letting os.File implementations get the writev optimization too. |
See also #21756. |
Step into this problem. net.Pipe impossible to use for tests with net.Buffers where there are multiple writers and single reader. Wire data became interleaved :( |
For Go 1.13 we should look at this earlier in the cycle. The part about os.File maybe implementing this suggests that if we do expose an API it should not refer to types in package net. Perhaps just [][]byte directly. |
I think the bigger problem here is for datagram sockets. The underlying system call guarantee a Edit: As for The current Edit2: |
As another voice: I'm particularly interested in what @rsc mentioned - having Personally I like the interface @zombiezen wrote down above and I would put it in |
I would love to have access to |
Although this issue only has 15 comments they all seem to head in different directions. I think that if the request is a |
I'm not convinced there's any scenario in which concurrent writers without holding a mutex is safe. The write(2) syscall can make short writes on e.g. interrupts, and the Go Write implementation will need a for loop around that -> concurrent Writes can interleave anyway. Writev matters for datagrams and performance. |
It didn't shipped with Go 1.14, right? Any plans to include that |
@tv42 "I'm not convinced there's any scenario in which concurrent writers without holding a mutex is safe. The write(2) syscall can make short writes on e.g. interrupts" |
@stokito ISTM that the meaning of "atomic" is under-specified there. Both the text in the wikipedia and the information from the manpage seem to suggest that it doesn't mean "either all writes succeed or none of them", but just that writes by different processes are not interleaved. i.e. it seems it refers to the isolation of ACID, not the atomicity. Also, from what I can tell, the actual POSIX standard does not guarantee even that, unless writes are less than In my experience, interpreting what guarantees the POSIX standard really makes is very subtle. And what is actually implemented even more so. Personally, I got convinced by the argument that short writes can happen, at least for my usecase. |
The
writev
syscall is supposed to act like a single write. TheWriteTo
method ofnet.Buffers
will make a single write syscall on writers that have the unexportedwriteBuffers
method. However, for writers that do not have such a method, it will callWrite
multiple times. This becomes significant if you are wrapping a*net.TCPConn
without embedding, for instance, since it has different performance characteristics with respect to Nagle's algorithm. Frustratingly, since thewriteBuffers
method is unexported, there's no way for the application to know the behavior ofBuffers.WriteTo
in order to work around the issue.Repro case: https://play.golang.org/p/rF0JRZs8z8
The text was updated successfully, but these errors were encountered: