https://github.com/python/cpython/commit/31f16e427b545f66a9a45ea9dd6c933975ce0e4c
commit: 31f16e427b545f66a9a45ea9dd6c933975ce0e4c
branch: main
author: Giovanni Siragusa <[email protected]>
committer: encukou <[email protected]>
date: 2024-12-02T14:18:30+01:00
summary:
gh-109523: Raise a BlockingIOError if reading text from a non-blocking stream
cannot immediately return bytes. (GH-122933)
files:
A Misc/NEWS.d/next/C_API/2024-08-12-10-15-19.gh-issue-109523.S2c3fi.rst
M Doc/library/io.rst
M Doc/whatsnew/3.14.rst
M Lib/_pyio.py
M Lib/test/test_io.py
M Misc/ACKS
M Modules/_io/textio.c
diff --git a/Doc/library/io.rst b/Doc/library/io.rst
index f793d7a7ef9a84..0d8cc5171d5476 100644
--- a/Doc/library/io.rst
+++ b/Doc/library/io.rst
@@ -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`.
@@ -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.
@@ -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)
@@ -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`:
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index f9322da3d4fbb0..75d027d33ccd16 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -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
----
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 42b0aea4e2eb2e..14961c39d3541d 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -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
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index f1f8ce57668f3b..81c17b2731cc58 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -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
diff --git a/Misc/ACKS b/Misc/ACKS
index fc4b83a0e2b823..913f7c8ecf5f1e 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1736,6 +1736,7 @@ Ng Pheng Siong
Yann Sionneau
George Sipe
J. Sipprell
+Giovanni Siragusa
Ngalim Siregar
Kragen Sitaker
Kaartic Sivaraam
diff --git
a/Misc/NEWS.d/next/C_API/2024-08-12-10-15-19.gh-issue-109523.S2c3fi.rst
b/Misc/NEWS.d/next/C_API/2024-08-12-10-15-19.gh-issue-109523.S2c3fi.rst
new file mode 100644
index 00000000000000..9d6b2e0c565623
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-08-12-10-15-19.gh-issue-109523.S2c3fi.rst
@@ -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.
diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index 0d851ee211511c..791ee070401fe5 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -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,
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]