gh-109532: clarify send() vs recv() behavior on a broken connection - #144747
gh-109532: clarify send() vs recv() behavior on a broken connection#144747Gh-Novel wants to merge 7 commits into
Conversation
…onnection Correct the claim that send() returns 0 bytes on a broken connection. In practice, send() on a broken connection raises OSError (EPIPE) rather than returning 0. Only recv() returns 0 bytes to indicate disconnection. Add a clarifying comment to the mysend example noting this distinction.
brijkapadia
left a comment
There was a problem hiding this comment.
I think that these are good (and important) changes. I think that the difference between how send and recv work for a closed connection could be better emphasized (see comment).
| that *there is no* :abbr:`EOT (End of Transfer)` *on a socket.* I repeat: if a socket | ||
| ``send`` or ``recv`` returns after handling 0 bytes, the connection has been | ||
| broken. If the connection has *not* been broken, you may wait on a ``recv`` | ||
| that *there is no* :abbr:`EOT (End of Transfer)` *on a socket.* I repeat: if a |
There was a problem hiding this comment.
These changes look good.
The one suggestion I have is to emphasize that calling send on a closed socket results in an OSError and thus should not actually happen in real code. In contrast, recv returning 0 for a closed socket is a feature. There is a paragraph above that explains what a return value of 0 from recv means, but I think there could be a better explanation of return values/exceptions forsend. Possibly say: In contrast, you should never call send on a broken socket, as it results in an OSError.
| that *there is no* :abbr:`EOT (End of Transfer)` *on a socket.* I repeat: if a socket | ||
| ``send`` or ``recv`` returns after handling 0 bytes, the connection has been | ||
| broken. If the connection has *not* been broken, you may wait on a ``recv`` | ||
| that *there is no* :abbr:`EOT (End of Transfer)` *on a socket.* I repeat: if a |
There was a problem hiding this comment.
| that *there is no* :abbr:`EOT (End of Transfer)` *on a socket.* I repeat: if a | |
| that *there is no* :abbr:`EOT (End of Transfer)` *on a socket.* If a |
There was a problem hiding this comment.
Thank you for the review! I've addressed both suggestions - removed "I repeat:" and restructured the paragraph to better emphasize that send() on a broken socket raises OSError (an error you should handle), in contrast to recv() returning 0 bytes as a deliberate disconnect signal.
Remove informal 'I repeat:' phrasing. Restructure paragraph to clearly contrast recv() returning 0 bytes (a deliberate signal of disconnection) with send() raising OSError (an error condition — should never be called on a broken socket). Co-authored-by: bkap123 <bkap123@users.noreply.github.com>
brijkapadia
left a comment
There was a problem hiding this comment.
Left one more suggestion, but otherwise LGTM!
Also as a reminder, if happen to be using an LLM, just remember these rules.
| @@ -201,6 +203,8 @@ length message:: | |||
| sent = self.sock.send(msg[totalsent:]) | |||
| if sent == 0: | |||
There was a problem hiding this comment.
I actually think that we can get rid of this if clause and the RuntimeError exception since send will already raise an error on a broken connection. That would also make more sense because your comment below is basically saying that send raises an error automatically so there is no reason to check its return value.
There was a problem hiding this comment.
Good point — dropped the if sent == 0 / RuntimeError from mysend and replaced it with a short comment noting that send() raises OSError on a broken connection. myreceive keeps its chunk == b'' check, since a 0-byte recv genuinely is the disconnect signal.
There was a problem hiding this comment.
Reopening this one, sorry — after replying I found that @serhiy-storchaka and @vstinner discussed this on the issue (#109532) after the PR was opened. POSIX doesn't guarantee the underlying send() never returns 0, so the guard can still fire on some platforms. More importantly, without it a 0 return never advances totalsent, so mysend would spin forever instead of failing loudly — a worse outcome than the RuntimeError. I've restored the check and added a comment making the point you raised: a broken connection surfaces as OSError, so the reader shouldn't treat a 0 return as the disconnect signal.
Documentation build overview
147 files changed ·
|
|
@Gh-Novel, please sign the CLI. |
|
This PR is stale because it has been open for 90 days with no activity. |
|
@serhiy-storchaka @vstinner — following up on your comments on #109532. I agree the existing sentence is defensible as written: it says that if So I've reframed this as a clarity fix rather than a correctness fix:
Does that wording work for you both? |
socket.sendbehavior #109532Summary
Clarifies how the Socket Programming HOWTO (
Doc/howto/sockets.rst) describessend()andrecv()when the remote end has disconnected.As noted on the issue, the existing sentence is defensible as written: it says that if
send()returns 0 the connection is broken, not that a broken connection makessend()return 0. The problem is that the combined phrasing — "if a socketsendorrecvreturns after handling 0 bytes, the connection has been broken" — reads as advice to detect disconnection fromsend()'s return value, when in practice the caller gets anOSError(typicallyBrokenPipeError/EPIPE). Onlyrecv()returning 0 is a deliberate disconnect signal.Changes
recv()returning 0 means the connection is broken, whilesend()on a broken socket will normally raiseOSError— no claim that a 0 return is impossible.if sent == 0guard in themysendexample, adding a comment explaining that it is a rare-case backstop: without it, a 0 return would never advancetotalsentand the loop would spin forever.Testing
📚 Documentation preview 📚: https://cpython-previews--144747.org.readthedocs.build/