gh-109523: Raise a BlockingIOError if reading text from a non-blocking stream cannot immediately return bytes. (GH-122933)

This commit is contained in:
Giovanni Siragusa 2024-12-02 14:18:30 +01:00 committed by GitHub
parent 930ba0ce60
commit 31f16e427b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 57 additions and 1 deletions

View File

@ -64,6 +64,12 @@ In-memory text streams are also available as :class:`StringIO` objects::
f = io.StringIO("some initial text data") 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 The text stream API is described in detail in the documentation of
:class:`TextIOBase`. :class:`TextIOBase`.
@ -770,6 +776,11 @@ than raw I/O does.
Read and return *size* bytes, or if *size* is not given or negative, until 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. 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, /) .. method:: read1(size=-1, /)
Read and return up to *size* bytes with only one call on the raw stream. Read and return up to *size* bytes with only one call on the raw stream.
@ -779,6 +790,10 @@ than raw I/O does.
.. versionchanged:: 3.7 .. versionchanged:: 3.7
The *size* argument is now optional. 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) .. class:: BufferedWriter(raw, buffer_size=DEFAULT_BUFFER_SIZE)
@ -1007,6 +1022,11 @@ Text I/O
.. versionchanged:: 3.10 .. versionchanged:: 3.10
The *encoding* argument now supports the ``"locale"`` dummy encoding name. 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 :class:`TextIOWrapper` provides these data attributes and methods in
addition to those from :class:`TextIOBase` and :class:`IOBase`: addition to those from :class:`TextIOBase` and :class:`IOBase`:

View File

@ -404,6 +404,15 @@ inspect
(Contributed by Zhikang Yan in :gh:`125634`.) (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 json
---- ----

View File

@ -2545,9 +2545,12 @@ def read(self, size=None):
size = size_index() size = size_index()
decoder = self._decoder or self._get_decoder() decoder = self._decoder or self._get_decoder()
if size < 0: if size < 0:
chunk = self.buffer.read()
if chunk is None:
raise BlockingIOError("Read returned None.")
# Read everything. # Read everything.
result = (self._get_decoded_chars() + result = (self._get_decoded_chars() +
decoder.decode(self.buffer.read(), final=True)) decoder.decode(chunk, final=True))
if self._snapshot is not None: if self._snapshot is not None:
self._set_decoded_chars('') self._set_decoded_chars('')
self._snapshot = None self._snapshot = None

View File

@ -3932,6 +3932,22 @@ def test_issue35928(self):
f.write(res) f.write(res)
self.assertEqual(res + f.readline(), 'foo\nbar\n') 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): class MemviewBytesIO(io.BytesIO):
'''A BytesIO object whose read method returns memoryviews '''A BytesIO object whose read method returns memoryviews

View File

@ -1736,6 +1736,7 @@ Ng Pheng Siong
Yann Sionneau Yann Sionneau
George Sipe George Sipe
J. Sipprell J. Sipprell
Giovanni Siragusa
Ngalim Siregar Ngalim Siregar
Kragen Sitaker Kragen Sitaker
Kaartic Sivaraam Kaartic Sivaraam

View File

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

View File

@ -1992,6 +1992,12 @@ _io_TextIOWrapper_read_impl(textio *self, Py_ssize_t n)
if (bytes == NULL) if (bytes == NULL)
goto fail; goto fail;
if (bytes == Py_None){
Py_DECREF(bytes);
PyErr_SetString(PyExc_BlockingIOError, "Read returned None.");
return NULL;
}
_PyIO_State *state = self->state; _PyIO_State *state = self->state;
if (Py_IS_TYPE(self->decoder, state->PyIncrementalNewlineDecoder_Type)) if (Py_IS_TYPE(self->decoder, state->PyIncrementalNewlineDecoder_Type))
decoded = _PyIncrementalNewlineDecoder_decode(self->decoder, decoded = _PyIncrementalNewlineDecoder_decode(self->decoder,