]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-109523: Raise a BlockingIOError if reading text from a non-blocking stream cannot...
authorGiovanni Siragusa <siracusano@gmail.com>
Mon, 2 Dec 2024 13:18:30 +0000 (14:18 +0100)
committerGitHub <noreply@github.com>
Mon, 2 Dec 2024 13:18:30 +0000 (14:18 +0100)
Doc/library/io.rst
Doc/whatsnew/3.14.rst
Lib/_pyio.py
Lib/test/test_io.py
Misc/ACKS
Misc/NEWS.d/next/C_API/2024-08-12-10-15-19.gh-issue-109523.S2c3fi.rst [new file with mode: 0644]
Modules/_io/textio.c

index f793d7a7ef9a84cbcc887b3fcc35acd65579361c..0d8cc5171d547686fc58b8a8175ebc58441cbe0e 100644 (file)
@@ -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`:
 
index f9322da3d4fbb018d701bbbd75255a36fd4c0e4a..75d027d33ccd164d6de98c8792db6ce0ba9790f7 100644 (file)
@@ -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
 ----
 
index 42b0aea4e2eb2ef3ddd37def32e27c0bd4132e78..14961c39d3541d743cb795185b36ffd8cc2c2e16 100644 (file)
@@ -2545,9 +2545,12 @@ class TextIOWrapper(TextIOBase):
                 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
index f1f8ce57668f3b57b8553356249641f447f256c6..81c17b2731cc5897df09c6fb8ba2dc3bc1205fae 100644 (file)
@@ -3932,6 +3932,22 @@ class TextIOWrapperTest(unittest.TestCase):
         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
index fc4b83a0e2b823c6cb123910ab09b08a58871c58..913f7c8ecf5f1e15ecde637d9222ac12928f7f90 100644 (file)
--- 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 (file)
index 0000000..9d6b2e0
--- /dev/null
@@ -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.
index 0d851ee211511c9f600d4e258c3ca499b12e6fcd..791ee070401fe5831965724445c7fcfda943219c 100644 (file)
@@ -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,