[Jython-checkins] jython (2.5): #1754: Fix modjy does not provide appropriate wsgi.input file-like object.

frank.wierzbicki jython-checkins at python.org
Thu Jul 5 18:48:11 CEST 2012


http://hg.python.org/jython/rev/bc783e270abc
changeset:   6780:bc783e270abc
branch:      2.5
parent:      6711:0ea5676c29a8
user:        Frank Wierzbicki <fwierzbicki at gmail.com>
date:        Thu Jul 05 09:24:49 2012 -0700
summary:
  #1754: Fix modjy does not provide appropriate wsgi.input file-like object.
Thanks for initial patch Philip Jenvey!

files:
  Lib/test/test_io_jy.py               |  66 ++++++++++++
  NEWS                                 |   4 +
  src/org/python/core/io/StreamIO.java |  85 +++++++++++++++-
  3 files changed, 154 insertions(+), 1 deletions(-)


diff --git a/Lib/test/test_io_jy.py b/Lib/test/test_io_jy.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_io_jy.py
@@ -0,0 +1,66 @@
+"""Misc io tests.
+
+Made for Jython.
+"""
+import unittest
+
+from org.python.core.util import FileUtil
+from org.python.core.io import StreamIO
+
+from java.io import InputStream
+from java.nio import ByteBuffer;
+
+class InfiniteInputStream(InputStream):
+
+    def read(self, *args):
+        if len(args) == 0:
+            return ord('x')
+        elif len(args) == 1:
+            return InputStream.read(self, args[0])
+        else:
+            return self.read_buffer(*args)
+
+    def read_buffer(self, buf, off, length):
+        if length > 0:
+            buf[off] = ord('x')
+            return 1
+        return 0
+
+
+class IoTestCase(unittest.TestCase):
+    """
+    Jython was failing to read all available content when an InputStream
+    returns early. Java's InputStream.read() is allowed to return less than the
+    requested # of bytes under non-exceptional/EOF conditions, whereas
+    (for example) wsgi.input requires the file.read() method to block until the
+    requested # of bytes are available (except for exceptional/EOF conditions).
+
+    See http://bugs.jython.org/issue1754 for more discussion.
+    """
+    def test_infinite_input(self):
+        iis = InfiniteInputStream()
+        f = FileUtil.wrap(iis, 'rb')
+        size = 10000
+        self.assertEqual(len(f.read(size)), size)
+        self.assertEqual(len(f.read(size)), size)
+        self.assertEqual(len(f.read(size)), size)
+
+    def test_buffer_no_array(self):
+        """
+        Directly tests StreamIO with and without a backing array and an
+        InputStream that returns early.
+        """
+        size = 10000
+        without_array = ByteBuffer.allocateDirect(size)
+        self.assertFalse(without_array.hasArray())
+        with_array = ByteBuffer.allocate(size)
+        self.assertTrue(with_array.hasArray())
+        bbs = [with_array, without_array]
+        for bb in bbs:
+            iis = InfiniteInputStream()
+            io = StreamIO(iis, True)
+            self.assertEqual(io.readinto(bb), size)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,10 @@
 Jython 2.7a1
   Bugs Fixed
 
+Jython 2.5.3b3
+  Bugs Fixed
+    - [ 1754 ] modjy does not provide appropriate wsgi.input file-like object
+
 Jython 2.5.3b2
   Bugs Fixed
     - [ 1908 ] Patch for 'Unmapped exception: java.net.NoRouteToHostException'
diff --git a/src/org/python/core/io/StreamIO.java b/src/org/python/core/io/StreamIO.java
--- a/src/org/python/core/io/StreamIO.java
+++ b/src/org/python/core/io/StreamIO.java
@@ -16,6 +16,8 @@
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 
+import java.nio.channels.spi.AbstractInterruptibleChannel;
+
 import org.python.core.Py;
 import org.python.modules.posix.PosixModule;
 
@@ -95,7 +97,7 @@
      *                close() (defaults to True)
      */
     public StreamIO(InputStream inputStream, boolean closefd) {
-        this(Channels.newChannel(inputStream), closefd);
+        this(newChannel(inputStream), closefd);
         this.inputStream = inputStream;
     }
 
@@ -107,6 +109,8 @@
      *                close() (defaults to True)
      */
     public StreamIO(OutputStream outputStream, boolean closefd) {
+        //XXX: It turns out we needed to write our own channel for
+        //     InputStreams. Do we need to do the same for OutputStreams?
         this(Channels.newChannel(outputStream), closefd);
         this.outputStream = outputStream;
     }
@@ -270,4 +274,83 @@
     public Channel getChannel() {
         return readable() ? readChannel : writeChannel;
     }
+
+    private static ReadableByteChannel newChannel(InputStream in) {
+        return new InternalReadableByteChannel(in);
+    }
+
+
+    /*
+     * AbstractInterruptibleChannel is used for its end() and begin() implementations
+     * but this Channel is not really interruptible.
+     */
+    private static class InternalReadableByteChannel
+        extends AbstractInterruptibleChannel
+        implements ReadableByteChannel {
+
+        private InputStream in;
+        private boolean open = true;
+
+        InternalReadableByteChannel(InputStream in) {
+            this.in = in;
+        }
+
+        public int read(ByteBuffer dst) throws IOException {
+            final int CHUNK = 8192;
+
+            int len = dst.remaining();
+            int totalRead = 0;
+            int bytesRead = 0;
+            if (dst.hasArray()) {
+                while (totalRead < len) {
+                    // array() can throw RuntimeExceptions but really shouldn't when
+                    // hasArray() is true
+                    try {
+                        begin();
+                        bytesRead = in.read(dst.array(), dst.arrayOffset(), dst.remaining());
+                    } finally {
+                        end(bytesRead > 0);
+                    }
+                    if (bytesRead < 0) {
+                        break;
+                    } else {
+                        dst.position(dst.position() + bytesRead);
+                        totalRead += bytesRead;
+                    }
+                }
+            } else {
+                byte buf[] = new byte[0];
+                while (totalRead < len) {
+                    int bytesToRead = Math.min((len - totalRead), CHUNK);
+                    if (buf.length < bytesToRead) {
+                        buf = new byte[bytesToRead];
+                    }
+                    try {
+                        begin();
+                        bytesRead = in.read(buf, 0, bytesToRead);
+                    } finally {
+                        end(bytesRead > 0);
+                    }
+                    if (bytesRead < 0) {
+                        break;
+                    } else {
+                        totalRead += bytesRead;
+                    }
+                    dst.put(buf, 0, bytesRead);
+                }
+            }
+            if ((bytesRead < 0) && (totalRead == 0)) {
+                return -1;
+            }
+            return totalRead;
+        }
+
+        protected void implCloseChannel() throws IOException {
+            if (open) {
+                in.close();
+                open = false;
+            }
+        }
+    }
+
 }

-- 
Repository URL: http://hg.python.org/jython


More information about the Jython-checkins mailing list