Skip to content

Commit

Permalink
gh-109523: Raise a BlockingIOError if reading text from a non-blockin…
Browse files Browse the repository at this point in the history
…g stream cannot immediately return bytes. (GH-122933)
  • Loading branch information
giosiragusa authored Dec 2, 2024
1 parent 930ba0c commit 31f16e4
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 1 deletion.
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 @@ -404,6 +404,15 @@ inspect
(Contributed by Zhikang Yan in :gh:`125634`.)



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 @@ -2545,9 +2545,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 @@ -3932,6 +3932,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 @@ -1736,6 +1736,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

0 comments on commit 31f16e4

Please sign in to comment.