non-blocking channel Infinite loop in java.util.Scanner
One of my student find a bug in the implementation of Scanner, that allows you to use a non blocking channel as input of a Scanner. The Scanner uses Channels.newReader() to create a Reader from a channel which itself create a StreamDecoder. In that case, StreamDecoder.impReader() goes into an infinite loop because impReader() calls readBytes() that does nothing if channel.read() returns zero. The javadoc of Channels.newReader() clearly states that it should throw a IllegalBlockingModeException but there is no code that checks that. I think a way to solve the problem is to insert a code that check the blocking state in Channels.newWriter(). if (ch instanceof SelectableChannel) { SelectableChannel sc = (SelectableChannel)ch; if (!sc.isBlocking()) throw new IllegalBlockingModeException(); } } and to document the exception in the constructor of Scanner that takes a channel. If someone provide me a bug id, it will provide a patch :) cheers, Rémi PS: The code below is a simple test to reproduce the infinite loop. ---------------------------------------------------- import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Scanner; public class Main { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(2332)); new Thread(new Runnable() { @Override public void run() { try { SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost", 2332)); do { channel.write(ByteBuffer.wrap(new byte[] {'A'})); Thread.sleep(1000); } while(true); } catch (IOException | InterruptedException e) { throw new AssertionError(e); } } }).start(); SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); Scanner scanner = new Scanner(socketChannel); while(scanner.hasNextLine()) { System.out.println(scanner.nextLine()); } } }
On 05/06/2012 11:00, Rémi Forax wrote:
One of my student find a bug in the implementation of Scanner, that allows you to use a non blocking channel as input of a Scanner.
The Scanner uses Channels.newReader() to create a Reader from a channel which itself create a StreamDecoder.
In that case, StreamDecoder.impReader() goes into an infinite loop because impReader() calls readBytes() that does nothing if channel.read() returns zero.
The javadoc of Channels.newReader() clearly states that it should throw a IllegalBlockingModeException but there is no code that checks that.
I think a way to solve the problem is to insert a code that check the blocking state in Channels.newWriter().
if (ch instanceof SelectableChannel) { SelectableChannel sc = (SelectableChannel)ch; if (!sc.isBlocking()) throw new IllegalBlockingModeException(); } } This could be fixed in Channels.newReader or in StreamDecoder, the former would be consistent with Channels.newWriter. I guess you know this already, but you will need to synchronize on the selectable channel's blockingLock to ensure that the blocking mode doesn't change.
I've created a bug for this: 7174305: (ch) Channels.newReader doesn't throw IllegalBlockingMode if channel is configured non-blocking -Alan.
On 06/05/2012 12:40 PM, Alan Bateman wrote:
On 05/06/2012 11:00, Rémi Forax wrote:
One of my student find a bug in the implementation of Scanner, that allows you to use a non blocking channel as input of a Scanner.
The Scanner uses Channels.newReader() to create a Reader from a channel which itself create a StreamDecoder.
In that case, StreamDecoder.impReader() goes into an infinite loop because impReader() calls readBytes() that does nothing if channel.read() returns zero.
The javadoc of Channels.newReader() clearly states that it should throw a IllegalBlockingModeException but there is no code that checks that.
I think a way to solve the problem is to insert a code that check the blocking state in Channels.newWriter().
if (ch instanceof SelectableChannel) { SelectableChannel sc = (SelectableChannel)ch; if (!sc.isBlocking()) throw new IllegalBlockingModeException(); } } This could be fixed in Channels.newReader or in StreamDecoder, the former would be consistent with Channels.newWriter. I guess you know this already, but you will need to synchronize on the selectable channel's blockingLock to ensure that the blocking mode doesn't change.
I've created a bug for this:
7174305: (ch) Channels.newReader doesn't throw IllegalBlockingMode if channel is configured non-blocking
-Alan.
Thanks Alan, I can't ensure that the blocking mode will not change by synchronizing on the channel's blockingLock because I will have to take the lock before creating the reader and releasing it when the reader is closed. An easier solution is to throw an IllegalBlockingModeException if the channel.read() returns 0, in that case I think I don't have to even check the configured mode (the question is what to do if the configured mode is changed by another thread between the call to read() and the call to isBlocking()). and BTW isBlocking() already takes the lock, may be the doc of isBlocking() should more clear about that. Rémi
On 06/06/2012 00:12, Rémi Forax wrote:
Thanks Alan,
I can't ensure that the blocking mode will not change by synchronizing on the channel's blockingLock because I will have to take the lock before creating the reader and releasing it when the reader is closed.
Looking at now, it would need to be done in StreamDecoder.readBytes.
An easier solution is to throw an IllegalBlockingModeException if the channel.read() returns 0, in that case I think I don't have to even check the configured mode (the question is what to do if the configured mode is changed by another thread between the call to read() and the call to isBlocking()).
It's possible that read may return bytes immediately when configured non-blocking so it means you will need to check the blocking mode to ensure that the expected IllegalBlockingModeException is thrown. I guess you could also check for 0 to avoid grabbing the blocking lock. -Alan.
participants (2)
-
Alan Bateman
-
Rémi Forax