views:

52

answers:

2

The JDK docs say, that if a thread is interrupted that currently blocks in an io operation of an InterruptibleChannel, the channel is closed and a ClosedByInterruptException is thrown. However, i get a different behaviour when using a FileChannel:

public class Main implements Runnable {

public static void main(String[] args) throws Exception {
    Thread thread = new Thread(new Main());
    thread.start();
    Thread.sleep(500);
    thread.interrupt();
    thread.join();
}

public void run() {
    try {
        readFile();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

private void readFile() throws IOException {
    FileInputStream in = new FileInputStream("large_file");
    FileChannel channel = in.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(0x10000);
    for (;;) {
        buffer.clear();
        // Thread.currentThread().interrupt();
        int r = channel.read(buffer);
        if (Thread.currentThread().isInterrupted()) {
            System.out.println("thread interrupted");
            if (!channel.isOpen())
                System.out.println("channel closed");
        }
        if (r < 0)
            break;
    }
}

}

Here, when the thread is interrupted, the read() call returns normally even so the channel has been closed. No exception is thrown. This code prints "thread interrupted" and "channel closed", and then on the next call to read() it throws a ClosedChannelException.

I wonder wether this behaviour is allowed. As i understand the docs, the read() should either return normally and not close the channel or close the channel and throw ClosedByInterruptException. Returning normally and closing the channel does not seem right. The trouble for my application is, that i get an unexpected and seemingly unrelated ClosedChannelException somewhere else, when a FutureTask that does io gets cancelled.

Note: The ClosedByInterruptException will get thrown as expected, when the thread is already interrupted when entering the read().

I have seen this behaviour using the 64-Bit Server-VM (JDK 1.6.0 u21, Windows 7). Can anyone confirm this?

A: 

One possibility is that you program actually reaches the end of file before the interrupt is delivered. Try changing your program to count the total number of bytes read and output that when you see that the current thread has been interrupted.

Stephen C
This is not the case, the return value of read() is positive, my testfile is very large. Also note that on end of file read() would just return -1 but not close the channel.
Gerald Thaler
+1  A: 

I remember reading somewhere, but I cannot quote the source here, that FileChannel is kinda interruptible. That once the read/write operation has passed from JVM to OS, the JVM cannot really do much, so the operation will take the time that it takes. The recommendation was to read/write in manageable size chunks, so JVM can check the thread interrupt status before handling the job to OS.

I think your example is a perfect demonstration of that behavior.

EDIT

I think the FileChannel behavior you describe violates principle of "least surprise", but from a certain angle works as expected, even, if you subscribe to that angle, as desired.

Because FileChannel is "kinda" interruptible, and the read/write operations are really blocking, then that blocking operation does succeed and returns valid data that represents the state of the file on disk. In case of a small file you may even get the whole contents of the file back. Because you have valid data, the designers of the 'FileChannel` class felt that you may want to use it, just before you start unwinding the interrupt.

I think that this behavior should be really, really well documented and for that you may submit a bug. However don't hold your breath on that ever being fixed.

I think the only way to say if thread is interrupted in the same loop iteration is to do what you are doing and check the thread interruption flag explicitly.

Alexander Pogrebnyak
I think I read about this in "Java NIO" book, but I don't have it handy at the moment.
Alexander Pogrebnyak
This is not about timing. Obviously the VM detects the thread interruption: It closes the channel. But the problem here is, that it does *not* throw a ClosedByInterruptException as advertised. Note that there is no close() call anywhere in my code, also no exception is thrown out of the interrupted read(). The VM just silently closes the channel without notification.
Gerald Thaler
@Gerald. I've added more to my answer.
Alexander Pogrebnyak
Thank you for your opinion Alexander. I agree that it makes sense for read() to return normally if it has data already. I have no problem with that. But why then do they also silently close the channel? Why not just leave it alone, when they return normally anyway? This does not make sense to me. I have to recheck for the channel close and close it myself if necessary anyway, because i cannot know where the interruption occured. All this seems really weird, it forces me to write complicated code whenever i call read(). I'll submit a bug report. This behaviour should at least be documented.
Gerald Thaler
@Gerald. I think they close the channel as a half-hearted attempt to follow the design spec of InterruptibleChannel ( channel gets interrupted, then channel gets closed ). This definitely prevents further manipulations of that channel. I think the best solution here would have been to throw an extension of ClosedByInterruptException and wrap the data that has been read in that new exception class.
Alexander Pogrebnyak