tags:

views:

1386

answers:

4

I created an ObjectInputSteam and ObjectOutputStream on a blocking SocketChannel and am trying to read and write concurrently. My code is something like this:

socketChannel = SocketChannel.open(destNode);
objectOutputStream = new ObjectOutputStream(Channels.newOutputStream(socketChannel));
objectInputStream = new ObjectInputStream(Channels.newInputStream(socketChannel));

Thread replyThread = new Thread("SendRunnable-ReplyThread") {
 @Override
 public void run() {
  try {
   byte reply = objectInputStream.readByte();//(A)
   //..process reply
  } catch (Throwable e) {
   logger.warn("Problem reading receive reply.", e);
  }
 }
};
replyThread.start();

objectOutputStream.writeObject(someObject);//(B)
//..more writing

Problem is the write at line (B) blocks until the read at line (A) completes (blocks on the object returned by SelectableChannel#blockingLock() ). But app logic dictates that the read will not complete until all the writes complete, so we have an effective deadlock.

SocketChannel javadocs say that concurrent reads and writes are supported.

I experienced no such problem when I tried a regular Socket solution:

Socket socket = new Socket();
socket.connect(destNode);
final OutputStream outputStream = socket.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
objectInputStream = new ObjectInputStream(socket.getInputStream());

However, then I cannot take advantage of the performance benefits of FileChannel#transferTo(...)

+1  A: 

If you want to use InputStream and OutputStream concurrently with SocketChannel, from looking at the source, it appears that you need to call SocketChannel.socket() and use the streams from that which behave slightly differently.

Tom Hawtin - tackline
I tried changing it to use socketChannel.socket().get(Out|In)putStream, but it made no difference. The OutputStream seems to be of the same type as before: the anonymous inner class returned by Channels#newOutputStream(OutputStream).
Kevin Wong
It's the InputStream which should change to a subclass (not tested).
Tom Hawtin - tackline
*checks bugster* http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=d76c6fabad9827be488fceb8d9f4?bug_id=4509080 Lots of bugs, low priority. Seems you are forced to start off with an old school java.net.Socket.
Tom Hawtin - tackline
+1  A: 

This seems to be a bug in java.nio.channels.Channels (thanks to Tom Hawtin; post it as an answer next time). A good description and workaround are described here (actually a duplicate of the bug Tom listed):

I tested the workaround and it works.

Kevin Wong
A: 

Interesting bug! You say though that you can't use FileChannel#transferTo. How about wrapping the I/O streams of the non-NIO socket into channels using Channesl#newChannel before passing to FileChannel#transferTo?

Alexander
I think `transferTo` only has performance benefits if the `WritableByteChannel` is a true NIO class for which it has specific support, not a wrapper of a regular IO class. Not sure, tho.
Kevin Wong
Come to think of it, I wonder if the wrapping the SocketChannel in a dummy ByteChannel as per the workaround described in one of the other answers will obviate the transferTo performance benefits as well.
Kevin Wong
A: 

The workaround in the bug report worked for me. It's worth noting that only one of input or output needs to be wrapped for the workaround to work - so if performance is especially important in one direction then you can wrap the less important one and be sure that the other will get all the optimisations available to it.

public InputStream getInputStream() throws IOException {
    return Channels.newInputStream(new ReadableByteChannel() {
  public int read(ByteBuffer dst) throws IOException {
   return socketChannel.read(dst);
  }
  public void close() throws IOException {
   socketChannel.close();
  }
  public boolean isOpen() {
   return socketChannel.isOpen();
  }
 });
}

public OutputStream getOutputStream() throws IOException {
    return Channels.newOutputStream(socketChannel);
}
Nick