-
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: 0-byte read from TCP connection always return EOF #10940
Comments
Please write an example program to show what you mean. Also, you neglected to mention which version of Go and what operating system. TCP connections are always in non-blocking mode, so they will never return a 0 byte read with no error except at EOF. |
I am using Go 1.4 on unix platforms (Linux and OSX). Below is the function I wrote to detect closed connections before attempting a Write:
Unfortunately, it always return true. As far as I can tell, all versions of go have a variation of the following:
So when syscall.read returns a n=0 bytes read, as it always will when 0 bytes are requested, this will always result in TCPConn.Read returning EOF Am I missing something? edit: Interestingly enough, if the TCP connection is wrapped in a TLS connection, I have the reverse problem, i.e. I never get an EOF even after the connection is closed. |
I don't think the Read with zero length buffer is a valid to determine TCP connections are fully duplex, and the read and write stream What if the remote end has shutdown the write end, so you will For example, The only way to determine whether you can write to a TCP BTW, according to Go's io.Reader definition, http://golang.org/pkg/io/#Reader This is working as intended. |
Let me backtrack and give a little bit of background: I have a service with an HTTP endpoint accepting JSON documents describing audit events to be fed to Splunk. Which means I am stuck with TCP or TCP+SSL and do not have the option to punt detection of packet loss or broken connection to a higher level application protocol. This service is currently written in Java, using Netty and I was hoping I could rewrite it in go to reduce RAM and disk footprint. My problem arise from the fact that Go does not offer any way for me to check the liveness of a TCP connection and will happily keep writing data into a closed socket. You are asserting that such a behavior is normal because of shortcomings of TCP, yet Netty has no apparent trouble detecting dropped connections and failed writes, which makes your claim hard to believe. I understand that whatever Netty is doing is not 100% accurate but quite frankly, I'll take 90% any day. Is low-level networking simply not a thing that go caters to? |
It's not a short coming of TCP. It's the design of TCP to be
able to close read/write stream separately.
And I've explained Read with zero length buffer has unspecified
behavior in Go (and more importantly, as Ian mentioned, it
doesn't map to read(2) syscall with n = 0)
Contrary to your claim, Go (along with the x/net subrepo) is
pretty good at low level networking applications.
|
Another view of the problem is that the remote side can be
gone at any moment, so the program have to be prepared
to handle Write calls that returns with n < len(buf).
But if that's correctly handled, why bother to check for close
before the write? If it's indeed closed before the write, you
will get n = 0. But this is no different if the connection is up
when you check but then it is down immediately after you
called Write. (classical TOCTTOU problem)
|
"But if that's correctly handled, why bother to check for close before the write?" Because, as I've tried to explain, the write always succeeds. Or rather, it always silently fails. i.e it returns a nil error and n == len(buf) I have unit tests where a dummy endpoints closes the connection. I can see the FIN packet in wireshark. The client connection just doesn't seem to care. In some cases if multiple subsequent sends are attempted, a RST may eventually be sent by the server and go will finally realize that the connection is unusable. The only reason I am looking for a way to manually check connection liveness (and in turn the only reason I asked a question about 0-byte read) is because of that surprising failure to honor the FIN in the first place. As explained above, Netty, among others, is perfectly capable of detecting the FIN and reacting appropriately. I would expect go to be able to do something similar: at the very least, writes to a socket having received a FIN should fail. Ideally there should be a way to manually test for that condition before attempting the write but I can live without that as long as writes don't silently fail. TOCTTOU is a race. The issue I am facing is not even close to being a race as evidenced by the fact that writes still silently fail several seconds after the socket is closed. Is this behavior intentional? If so what is the rationale and is there any way to work around it without forking the net package? |
If a write succeeds with a non-zero-sized buffer on a closed connection, that is clearly a bug. Can you show a standalone program that demonstrates this? |
test code
output on my machine:
|
From the wireshark capture, the client doesn't know the server For example, the tcpdump for my example 0.397171 IP client > server: Flags [S], seq 564126983, win 43690, options [..], length 0 (For demonstration purposes, I added an one second sleep to the The first part looks almost exactly the same as your wireshark |
Your example does not correspond to my use case: the server I have to deal with never writes to the client and your code doesn't close the read end of the server. Even if I change my test such that
the client still allows write after the server calls |
I don't see what Go can do here. Try using "go test -c" to build your test into an executable and then run "strace -f EXECUTABLE". You will see that the final write system call succeeds and the kernel reports it as succeeding. Go is just returning that success to the caller. The same thing would happen for a C program. At the TCP level, you have closed one side of the TCP connection but not the other. The kernel will accept writes until both sides are closed. This is not as simple a matter as you seem to be expecting. I found a decent description of the issue at http://stackoverflow.com/questions/11436013/writing-to-a-closed-local-tcp-socket-not-failing . You can get the result you want in your test program by adding this line just after the successful call to l.Accept: |
FWIW, the 0-byte-Read-always-returns-EOF was changed in Go 1.7 with 5bcdd63. But as of Go 1.7 it now always returns nil, which is at least a bit more Go-like, but still doesn't tell you whether FIN was ever seen arriving. There aren't great APIs anywhere (especially not portably) to know that without reading data. |
My understanding is that the recommended way to test whether a TCP connection has been closed by the remote peer is to do a 0-byte read from the socket, which would return EOF if the remote peer sent a FIN.
Unfortunately that doesn't work in go as netFD.Read always return EOF when 0 bytes are read, regardless of the requested read size.
Is that on purpose? Is there another way to detect reception of a FIN? This matters to me because I need to stream data to a write-only raw TCP endpoint that I don't control and the first Write on a closed connection often silently fails, potentially resulting in data loss.
The text was updated successfully, but these errors were encountered: