Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: Are concurrent reads and writes possible on a blocking SocketChannel via Object(In|Out)putStreams?

Tags:

java

sockets

nio

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(...)

like image 209
Kevin Wong Avatar asked Oct 06 '08 15:10

Kevin Wong


3 Answers

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.

like image 176
Kevin Wong Avatar answered Sep 29 '22 21:09

Kevin Wong


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);
}
like image 31
Nick Avatar answered Sep 29 '22 21:09

Nick


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.

like image 40
Tom Hawtin - tackline Avatar answered Sep 29 '22 20:09

Tom Hawtin - tackline