Skip to content

Handle read() returning None on a non-blocking socket - #839

Open
afonsojanu wants to merge 1 commit into
cherrypy:mainfrom
afonsojanu:fix/handle-none-read-on-nonblocking-socket
Open

Handle read() returning None on a non-blocking socket#839
afonsojanu wants to merge 1 commit into
cherrypy:mainfrom
afonsojanu:fix/handle-none-read-on-nonblocking-socket

Conversation

@afonsojanu

Copy link
Copy Markdown

Fixes #278.

When cheroot serves with a non-blocking socket (timeout=0), io.BufferedReader.read() can legitimately return None instead of b'' when there's nothing to read yet - that's the documented contract for io.RawIOBase in non-blocking mode, not an error condition. StreamReader.read() overrides read() to track bytes_read but didn't account for this, so it crashed with TypeError: object of type 'NoneType' has no len() any time a non-blocking read came back with nothing available.

I confirmed this is still live on current main, not just a Python 2 quirk as the original report speculated - reproduced it with a real non-blocking socket pair (no data sent yet, read() blows up immediately).

The fix just skips the byte count update when val is None and returns it unchanged, so the rest of the io stack still sees the documented would-block signal it already knows how to handle.

Added a regression test using the existing MockSocket pattern (its recv_into now raises BlockingIOError to simulate the non-blocking-no-data case) - fails on unmodified makefile.py with the exact same TypeError from the issue, passes with the fix. Ran the full test_makefile.py, test_conn.py, and test_wsgi.py suites before and after, all green.

Under a non-blocking socket (cheroot supports this via timeout=0),
io.BufferedReader.read() can legitimately return None instead of b''
when there's no data available yet - that's documented behavior in
_pyio's raw IO contract, not an error case.

StreamReader.read() didn't account for this and crashed with
TypeError: object of type 'NoneType' has no len() the moment a
non-blocking read came back empty, which in practice meant it could
blow up mid-request whenever the client hadn't sent anything yet.

Fixes cherrypy#278.
@read-the-docs-community

Copy link
Copy Markdown

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.21%. Comparing base (edef8ff) to head (758d609).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #839      +/-   ##
==========================================
- Coverage   78.37%   78.21%   -0.16%     
==========================================
  Files          41       41              
  Lines        4791     4802      +11     
  Branches      548      550       +2     
==========================================
+ Hits         3755     3756       +1     
- Misses        899      906       +7     
- Partials      137      140       +3     

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] We should account for when io.BufferedReader.read() returns None in makefile

2 participants