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(...)
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.
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);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With