-
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
io: Copy is easy to misuse and leak goroutines blocked on reads #58628
Comments
I don't understand what would be reading from that channel. How does it help with the |
package main
import (
"fmt"
"io"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "localhost:11111")
if err != nil {
panic(err)
}
defer conn.Close()
_, err = io.Copy(os.Stdout, conn)
fmt.Println("copy exited:", err)
} If I listen using
I think you have some other bug and this is not related to
They already do by returning an error on |
Another goroutine that can close the read side. @Jorropo The issue here is that the read side is never closed, only the write side. |
So the issue here is that Is the paragraph above correct ?
This is important for the setup here, because if |
If I understand correctly, you are suggesting that an |
@ianlancetaylor It could indeed be, but since the runtime already has epoll (or equivalent on other platforms) wired up it seemed like a good case to generalize. |
Closing a descriptor is not the same as closing a |
It isn't very clear from your description which of the following situations you are in.
|
@rittneje In the specific case with moby, we have a long running process (ie a container) which we allow API users to attach to the stdio streams of. Basically there is no facility built-in to go to know when a fd has closed other than by trying to read/write on it (perhaps some other socket op would do as well). |
Both In particular, if you don't expect the other side to write anything more to the socket, you could call That is, I would expect a realistic example to look more like: var (
dst io.ReadWriteCloser = …
src io.ReadCloser = …
)
errc := make(chan error, 1)
go func() {
_, err := io.Copy(io.Discard, dst)
// The connection to dst is broken.
// Close src to interrupt the io.Copy from it.
src.Close()
errc <- err
}()
_, srcErr := io.Copy(dst, src)
// Close dst to interrupt the io.Copy from it.
dst.Close()
dstErr := <-errc |
@bcmills We do use the read side. Your example also doesn't work if/when the client does a close-write on the socket, which wouldn't be completely unheard of it the client has nothing to send and is only expecting to receive. |
@cpuguy83 My question was really more getting to the fact that your proposed
Also, I assume that currently the runtime poller only polls if you are currently trying to read/write with the fd. But for |
I will also point out that For remote TCP connections in particular, you need some sort of application-level ping between processes in order to properly deal with half-closed connections. On a somewhat related note, Certainly there are use cases for |
@rittneje On Linux I would expect this to be waiting for I don't think it's particularly useful to say |
@cpuguy83 Again, AFAIK, you cannot poll an fd until |
@rittneje You are right |
I have also encountered this issue of it just blocking for ever in a TCP server setup. What I have done is set the deadline on a connection: timeoutDuration := 1 * time.Second
proxyConn.Conn.SetDeadline(time.Now().Add(timeoutDuration)) its not perfect and I really wish the io.Copy had some lower level implementation to prevent it from blocking for ever. |
What version of Go are you using (
go version
)?All
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?N/A
What did you do?
Because of the way io.Copy works (and really any other solution to the same need) it is very easy to wedge a goroutine, and probably an fd along with it.
Example:
In this case, the goroutine will leak once pw2 is closed because there is no way to cancel that copy without also closing pr1.
In some cases this makes sense, especially if you control both pipes.
In docker/moby this issue is a common case where we have a client connected over a unix or TCP socket, it is attached to a container's stdio streams.
If the client disconnects there is no mechanism (aside from platform specific code like epoll) to get notified that the copy operation needs to be cancelled.
I suggest an interface be added, potentially to
io
to receive close notifications from the runtime.Example:
The actual interface could be something like (maybe something that returns a context that gets cancelled?).
I'm not married to the above interface (I know there's a similarly named one in
net/http
).*os.File
and net.Conn implementations could implement this interface to help with the above scenario, and since the go runtime is already doing a bunch of fd notification magic it should generally be easily supported in the stdlib across platforms.The text was updated successfully, but these errors were encountered: