Friday, May 4, 2012

Java 7: NIO.2 I/O operations on asynchronous channels are not atomic

This part of my NIO.2 series wasn't on schedule when I started writing about NIO.2 aasynchronous file channels. It deals with an important detail: read and write operations are not atomic. What that means is that AsynchronousFileChannel -> write() does not garantee to write all bytes passed as parameter to the destination file. Instead it returns the number of bytes written as return parameters of the corresponding I/O operations and the client needs to deal with situations where the bytes written isn't equal to the remaining bytes in the passed ByteBuffer.

Let's recall the method signatures of the read() and write() operations in the AsynchronousFileChannel interface for a moment.

public abstract <A> void write(ByteBuffer src,
                                   long position,
                                   A attachment,
                                   CompletionHandler<Integer,? super A> handler);

    public abstract <A> void read(ByteBuffer dst,
                                  long position,
                                  A attachment,
                                  CompletionHandler<Integer,? super A> handler);

As you can see these signatures offer to pass a completion handler. I've already intruduced the completion handler in my last blog about closing file channels safely. You could also use the completion handler to enforce that all bytes are written or read when you perform I/O operations on an asynchronous channel. Here is the code snippet that does the Job.

The readAll (line 14) and the writeFully methods (line 35) both call the corresponding read or write operations on asynchronous file channels recursively. This recursion ends, when the bytes of the source ByteBuffer are transferred completely. Notice that the main thread has to wait for these recursions to finnish. Therefore a CountDownLatch stops the main thread until all bytes are processed by the I/O thread that executes the CompletionHandler.

The explained procedure works because the position of the source or destination ByteBuffer is always in sync with the actual bytes transferred. Another important fact is that the write() and read() operations in the CompletionHandler are chained. That is when the write task completes one new one is issued and when this completes one new one is issued and so forth. Allthough different JVM threads will participate, there won't be an issue in sharing that same (non thread safe) ByteBuffer instance.

The NIO.2 file channels series:
- Introduction
- Applying custom thread pools
- Closing file channels without loosing data
- I/O operations are not atomic

1 comment:

  1. Sorry but I don't get it... if we have to block (using latch.await()) then what's the point in using asynchronous IO. Might as well just use BufferedOutputStream. Am I missing something?