Skip to content

gh-109532: clarify send() vs recv() behavior on a broken connection - #144747

Open
Gh-Novel wants to merge 7 commits into
python:mainfrom
Gh-Novel:fix-issue-109532
Open

gh-109532: clarify send() vs recv() behavior on a broken connection#144747
Gh-Novel wants to merge 7 commits into
python:mainfrom
Gh-Novel:fix-issue-109532

Conversation

@Gh-Novel

@Gh-Novel Gh-Novel commented Feb 12, 2026

Copy link
Copy Markdown

Summary

Clarifies how the Socket Programming HOWTO (Doc/howto/sockets.rst) describes send() and recv() 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 makes send() return 0. The problem is that the combined phrasing — "if a socket send or recv returns after handling 0 bytes, the connection has been broken" — reads as advice to detect disconnection from send()'s return value, when in practice the caller gets an OSError (typically BrokenPipeError/EPIPE). Only recv() returning 0 is a deliberate disconnect signal.

Changes

  • Separated the two cases in the prose: recv() returning 0 means the connection is broken, while send() on a broken socket will normally raise OSError — no claim that a 0 return is impossible.
  • Kept the if sent == 0 guard in the mysend example, adding a comment explaining that it is a rare-case backstop: without it, a 0 return would never advance totalsent and the loop would spin forever.

Testing

  • Sphinx doc build passes with zero warnings.

📚 Documentation preview 📚: https://cpython-previews--144747.org.readthedocs.build/

…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 brijkapadia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread Doc/howto/sockets.rst Outdated
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Doc/howto/sockets.rst Outdated
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 brijkapadia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one more suggestion, but otherwise LGTM!
Also as a reminder, if happen to be using an LLM, just remember these rules.

Comment thread Doc/howto/sockets.rst
@@ -201,6 +203,8 @@ length message::
sent = self.sock.send(msg[totalsent:])
if sent == 0:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@python-cla-bot

python-cla-bot Bot commented Apr 29, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@read-the-docs-community

read-the-docs-community Bot commented Apr 29, 2026

Copy link
Copy Markdown

@serhiy-storchaka

Copy link
Copy Markdown
Member

@Gh-Novel, please sign the CLI.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 90 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Sep 4, 2026
@Gh-Novel

Gh-Novel commented Sep 5, 2026

Copy link
Copy Markdown
Author

@serhiy-storchaka @vstinner — following up on your comments on #109532.

I agree the existing sentence is defensible as written: it says that if send() returns 0 the connection is broken, not that a broken connection makes send() return 0. My concern is narrower — readers take the combined phrasing ("if a socket send or recv returns after handling 0 bytes, the connection has been broken") as advice to detect disconnection from send()'s return value, when in practice they'll get an OSError.

So I've reframed this as a clarity fix rather than a correctness fix:

  • recv() returning 0 is described as the deliberate disconnect signal it is;
  • send() on a broken socket is described as normally raising OSError — no claim that a 0 return is impossible;
  • the if sent == 0 guard stays in the example, with a comment noting it's a rare-case backstop against looping forever.

Does that wording work for you both?

@Gh-Novel Gh-Novel changed the title gh-109532: fix socket HOWTO inaccuracy about send() on broken connection gh-109532: clarify send() vs recv() behavior on a broken connection Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting review docs Documentation in the Doc dir skip news stale Stale PR or inactive for long period of time.

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants