powered by Jive Software

IOException using IBB stream


I’m trying to use NIO Channels to transfer files using Smack.

Basically, this is the code for retrieving a file and writing it into a file on the local machine:


FileChannel fc = new FileOutputStream(filename).getChannel();

InputStream in = request.accept().recieveFile();ReadableByteChannel rbc = java.nio.channels.Channels.newChannel(in); long pos = 0L;
while (true) {
  long transferred = fc.transferFrom(rbc, pos, 99999999);
  if (transferred == 0) {
    // check for EOF

        if (in.read() == -1) break; // <== IOException "Stream is closed"

  } else pos += transferred;

Note, that this code is just for illustration purposes – the real code has no access to the FileTransferRequest nor the IncomingFileTransfer.

Unfortunately, with transferFrom, there’s no indication as to whether the stream already reached EOF. That’s why an extra check is used in order to break out of the loop when that happens.

For any other kind of stream, this code works very well. But, an IBBInputStream behaves special, because it gets auto-closed when it hits EOF, which is quite annoying.

Usually, for any InputStream if you reach EOF, the stream stays open, one can call read() ad nauseam which will always return -1.

I suggest that the implementation should be changed to differentiate between EOF and the closed state and, in particular, that a stream is only ever in the closed state after explicitely calling its close method.

\edit: here’s the corresponding stack trace:


     [java.io.IOException: Stream is closed
        at org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession$IBBInputStream.checkClosed(InBandBytestreamSession.java:395)
        at org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession$IBBInputStream.read(InBandBytestreamSession.java:305)
        at java.nio.channels.Channels$ReadableByteChannelImpl.read(Channels.java:385)
        at sun.nio.ch.FileChannelImpl.transferFromArbitraryChannel(FileChannelImpl.java:617)
        at sun.nio.ch.FileChannelImpl.transferFrom(FileChannelImpl.java:655)


I’ve created SMACK-468.

We had a similar post a while ago http://community.igniterealtime.org/message/215974 Will look into it.


I just had a look at it myself.

I think this should do it:

Index: source/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java
--- source/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java (Revision 13817)
+++ source/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java (Arbeitskopie)
@@ -388,8 +388,9 @@
          * @throws IOException if stream is closed and no data should be read anymore
         private void checkClosed() throws IOException {
-            /* throw no exception if there is data available, but not if close method was invoked */
-            if ((isClosed && this.dataQueue.isEmpty()) || closeInvoked) {
+            /* throw an exception if, and only if, this stream has been already closed
+             * by the user using the close() method */
+            if (this.closeInvoked) {
                 // clear data queue in case additional data was received after stream was closed
                 throw new IOException("Stream is closed");
@@ -401,7 +402,7 @@
         }          public void close() throws IOException {
-            if (isClosed) {
+            if (this.closeInvoked) {

What do you think?

While this may fix the problem, I still wonder why the original author designed checkClosed that way.

Why is the change in close() necessary? Maybe you could elabroate your changes a bit.

I can only speculate. I guess the original author just couldn’t resist the temptation of combining the internal XMPP stream closing with the closing of the *external *InputStream interface. He/she probably thought it would be a good idea to share the “closed” state between the XMPP universe and the Java interface.

But, what you do with the XMPP stream internally is totally irrelevant to the InputStream contract. A Java InputStream just does not close suddenly.

That basically explains the change in the close() method: it is only *ever *regarded as “closed” if the user explicitely chose to close it.

When the XMPP stream is closed just means you cannot read any data from the InputStream.



Since RC1 of Smack 4.0.0 already hit the streets, any chance to include the fix for SMACK-468 ?


Pushed https://github.com/Flowdalic/Smack/commit/a799749f4315d12427cf9f6181a0e861c8c301 7a

Would be great if you could test and report back. Thank you.