TCP is UNreliable
cliffc.org
cliffc.org
When you call shutdown() to arrange a FIN for a socket, especially after you've sent data and received no application-layer acknowledgement, you're supposed to verify that the connection closed properly. You do this by select()ing on the socket and then read()ing 0 bytes.
The error condition (the RST segment they're getting) is signaled by the "Connection closed by peer" errno --- and -1 from read().
The reliability signals this app needs are built into both TCP and the socket programming interface, from what I can tell. The application-layer acknowledgement it wants appears duplicative.
An ACK only suggests that a segment has reached the recieve buffer successfully. If the receiving application crashes between buffering incoming data and successfully processing it, that's expressly not TCP's problem. The RST will signal that the receiver has died, but does not imply whether or not the application has successfully processed any buffered data up to that point.
AFAIK, an application-layer acknowledgement is the only way to solve this problem.
However, there are other cases where you don't need lock-step app-layer acknowledgements; for instance, if your messages are idempotent.
I'm really just reacting to the forensic work in this article, which suggests (maybe I'm misreading) a problem that TCP doesn't actually have.
I struggle to understand why an RPC system (as detailed in the article) wouldn't want an app-level acknowledgement though...
This would be the most robust fix. Have the server return "OK" or something after it has processed the data. It could have silently crashed but still accept connections.
I'm not saying there's never any point to application acknowledgements; look at any careful SMTP server implementation for a study of how to handle these problems. TCP can promise a reliable transport but obviously not a reliable disk.
If the peer crashes just as it read() the data, but didn't process it, it will look just as a normal clean close on the sender machine if there arn't more data laying around in the TCP buffers.
Mistake #1: The sender optimistically sends a first packet before getting a SYN reply. That implies that the sender issued a non-blocking connect, and then wrote without waiting for the connect to complete. Otherwise, the sender (kernel) wouldn't have any data to send right away.
Mistake #2: The sender sends a FIN, but doesn't block because all data has already been acknowledged. This is supposedly a problem because the app never receives the data, but a TCP ack has never meant that data got beyond the receiving kernel to an app.
So yes, it's true that neither side ever got an error and unfortunate that it's possible for the receiving app to get no data in that case, but that doesn't make it a TCP problem. Somebody at a higher level - JVM or the app itself - over-optimized their "connection and first data" code without understanding the actual behavior of the TCP protocol and socket API. Then they "fixed" it by working with their own protocol instead of against the standard one.
Nothing to see here. It was just a false alarm. Move on.
The only thing slightly fishy is that the peer application didn't receive any connection attempt, but if you're anyway quite overloaded, e.g. running out of file descriptors, that may well happen.
The caveat here is that you close() the socket when you're done. close() does not, by default, wait until the remote end also closed its end. In the scenario described above, the remote end would eventually signal an error, but if you shoved the data into the remote kernel buffers, close and forget about that connection, your app will never see that error.