Skip to content

gh-109523: Reading text from a non-blocking stream with read may now raise a BlockingIOError if the operation cannot immediately return bytes. #122933

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

Merged
merged 22 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
61cdfc5
Add unittest to handle non-blocking read scenarios.
giosiragusa Aug 12, 2024
5123ff6
📜🤖 Added by blurb_it.
blurb-it[bot] Aug 12, 2024
7a9037f
Update documentation for Text I/O and Buffered Streams to include han…
giosiragusa Aug 15, 2024
c31b0c7
Fix double backticks for inline literals.
giosiragusa Aug 15, 2024
ade7db0
Add a more meaningful message for the BlockingIOError.
giosiragusa Aug 15, 2024
0953ad0
Updated the TextIOWrapper test to use self.io.open() instead of open(…
giosiragusa Aug 15, 2024
100c4cb
- Raise `BlockingIOError` in `TextIOWrapper.read()` in `_pyio.py` whe…
giosiragusa Aug 15, 2024
bf5cc2b
Update NEWS entry to include references to _io and _pyio for Blocking…
giosiragusa Aug 17, 2024
fc02b20
Align documentation and code with Python style practices
giosiragusa Aug 17, 2024
c2d14e3
Fix news message for sphinx-lint
giosiragusa Aug 17, 2024
a735fff
Fix news message for sphinx-lint
giosiragusa Aug 17, 2024
5f1cbae
Fix news message for sphinx-lint
giosiragusa Aug 17, 2024
cabdf39
Make notes more consistent
giosiragusa Aug 17, 2024
8286a0a
Align BlockingIOError message in _pyio.py with the C implementation i…
giosiragusa Sep 19, 2024
8690397
Add blank line after note.
giosiragusa Nov 27, 2024
2f7fbb2
Remove unnecessary blank lines.
giosiragusa Nov 27, 2024
dbd807d
Keep the error message simple.
giosiragusa Nov 27, 2024
1ac9096
Release the reference before throwing the exception.
giosiragusa Nov 27, 2024
7d05cc3
Add blank line after notes.
giosiragusa Nov 27, 2024
9f1c5f4
Remove unnecessary blank lines.
giosiragusa Nov 27, 2024
317d402
Added what's new entry.
giosiragusa Nov 29, 2024
8e4e661
Update Doc/whatsnew/3.14.rst
giosiragusa Nov 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Doc/library/io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ In-memory text streams are also available as :class:`StringIO` objects::

f = io.StringIO("some initial text data")

.. note::

When working with a non-blocking stream, be aware that read operations on text I/O objects
might raise a :exc:`BlockingIOError` if the stream cannot perform the operation
immediately.

The text stream API is described in detail in the documentation of
:class:`TextIOBase`.

Expand Down Expand Up @@ -770,6 +776,11 @@ than raw I/O does.
Read and return *size* bytes, or if *size* is not given or negative, until
EOF or if the read call would block in non-blocking mode.

.. note::

When the underlying raw stream is non-blocking, a :exc:`BlockingIOError`
may be raised if a read operation cannot be completed immediately.

.. method:: read1(size=-1, /)

Read and return up to *size* bytes with only one call on the raw stream.
Expand All @@ -779,6 +790,10 @@ than raw I/O does.
.. versionchanged:: 3.7
The *size* argument is now optional.

.. note::

When the underlying raw stream is non-blocking, a :exc:`BlockingIOError`
may be raised if a read operation cannot be completed immediately.

.. class:: BufferedWriter(raw, buffer_size=DEFAULT_BUFFER_SIZE)

Expand Down Expand Up @@ -1007,6 +1022,11 @@ Text I/O
.. versionchanged:: 3.10
The *encoding* argument now supports the ``"locale"`` dummy encoding name.

.. note::

When the underlying raw stream is non-blocking, a :exc:`BlockingIOError`
may be raised if a read operation cannot be completed immediately.

:class:`TextIOWrapper` provides these data attributes and methods in
addition to those from :class:`TextIOBase` and :class:`IOBase`:

Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ Added support for converting any objects that have the
:meth:`!as_integer_ratio` method to a :class:`~fractions.Fraction`.
(Contributed by Serhiy Storchaka in :gh:`82017`.)


io
--

* Reading text from a non-blocking stream with ``read`` may now raise a
:exc:`BlockingIOError` if the operation cannot immediately return bytes.
(Contributed by Giovanni Siragusa in :gh:`109523`.)


json
----

Expand Down
5 changes: 4 additions & 1 deletion Lib/_pyio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2523,9 +2523,12 @@ def read(self, size=None):
size = size_index()
decoder = self._decoder or self._get_decoder()
if size < 0:
chunk = self.buffer.read()
if chunk is None:
raise BlockingIOError("Read returned None.")
# Read everything.
result = (self._get_decoded_chars() +
decoder.decode(self.buffer.read(), final=True))
decoder.decode(chunk, final=True))
if self._snapshot is not None:
self._set_decoded_chars('')
self._snapshot = None
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -3919,6 +3919,22 @@ def test_issue35928(self):
f.write(res)
self.assertEqual(res + f.readline(), 'foo\nbar\n')

@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
def test_read_non_blocking(self):
import os
r, w = os.pipe()
try:
os.set_blocking(r, False)
with self.io.open(r, 'rt') as textfile:
r = None
# Nothing has been written so a non-blocking read raises a BlockingIOError exception.
with self.assertRaises(BlockingIOError):
textfile.read()
finally:
if r is not None:
os.close(r)
os.close(w)


class MemviewBytesIO(io.BytesIO):
'''A BytesIO object whose read method returns memoryviews
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,7 @@ Ng Pheng Siong
Yann Sionneau
George Sipe
J. Sipprell
Giovanni Siragusa
Ngalim Siregar
Kragen Sitaker
Kaartic Sivaraam
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reading text from a non-blocking stream with ``read`` may now raise a :exc:`BlockingIOError` if the operation cannot immediately return bytes.
6 changes: 6 additions & 0 deletions Modules/_io/textio.c
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,12 @@ _io_TextIOWrapper_read_impl(textio *self, Py_ssize_t n)
if (bytes == NULL)
goto fail;

if (bytes == Py_None){
Py_DECREF(bytes);
PyErr_SetString(PyExc_BlockingIOError, "Read returned None.");
return NULL;
}

_PyIO_State *state = self->state;
if (Py_IS_TYPE(self->decoder, state->PyIncrementalNewlineDecoder_Type))
decoded = _PyIncrementalNewlineDecoder_decode(self->decoder,
Expand Down
Loading