| /* |
| * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* @test |
| * @bug 4607272 6822643 6830721 6842687 |
| * @summary Unit test for AsynchronousFileChannel |
| * @key randomness |
| */ |
| |
| import java.nio.file.*; |
| import java.nio.channels.*; |
| import java.nio.ByteBuffer; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.*; |
| import java.util.concurrent.*; |
| import java.util.concurrent.atomic.AtomicReference; |
| import static java.nio.file.StandardOpenOption.*; |
| |
| public class Basic { |
| |
| private static final Random rand = new Random(); |
| |
| public static void main(String[] args) throws IOException { |
| // create temporary file |
| File blah = File.createTempFile("blah", null); |
| blah.deleteOnExit(); |
| |
| AsynchronousFileChannel ch = AsynchronousFileChannel |
| .open(blah.toPath(), READ, WRITE); |
| try { |
| // run tests |
| testUsingCompletionHandlers(ch); |
| testUsingWaitOnResult(ch); |
| testInterruptHandlerThread(ch); |
| } finally { |
| ch.close(); |
| } |
| |
| // run test that expects channel to be closed |
| testClosedChannel(ch); |
| |
| // these tests open the file themselves |
| testLocking(blah.toPath()); |
| testCustomThreadPool(blah.toPath()); |
| testAsynchronousClose(blah.toPath()); |
| testCancel(blah.toPath()); |
| testTruncate(blah.toPath()); |
| |
| // eagerly clean-up |
| blah.delete(); |
| } |
| |
| /* |
| * Generate buffer with random contents |
| * Writes buffer to file using a CompletionHandler to consume the result |
| * of each write operation |
| * Reads file to EOF to a new buffer using a CompletionHandler to consume |
| * the result of each read operation |
| * Compares buffer contents |
| */ |
| static void testUsingCompletionHandlers(AsynchronousFileChannel ch) |
| throws IOException |
| { |
| System.out.println("testUsingCompletionHandlers"); |
| |
| ch.truncate(0L); |
| |
| // generate buffer with random elements and write it to file |
| ByteBuffer src = genBuffer(); |
| writeFully(ch, src, 0L); |
| |
| // read to EOF or buffer is full |
| ByteBuffer dst = (rand.nextBoolean()) ? |
| ByteBuffer.allocateDirect(src.capacity()) : |
| ByteBuffer.allocate(src.capacity()); |
| readAll(ch, dst, 0L); |
| |
| // check buffers are the same |
| src.flip(); |
| dst.flip(); |
| if (!src.equals(dst)) { |
| throw new RuntimeException("Contents differ"); |
| } |
| } |
| |
| /* |
| * Generate buffer with random contents |
| * Writes buffer to file, invoking the Future's get method to wait for |
| * each write operation to complete |
| * Reads file to EOF to a new buffer, invoking the Future's get method to |
| * wait for each write operation to complete |
| * Compares buffer contents |
| */ |
| static void testUsingWaitOnResult(AsynchronousFileChannel ch) |
| throws IOException |
| { |
| System.out.println("testUsingWaitOnResult"); |
| |
| ch.truncate(0L); |
| |
| // generate buffer |
| ByteBuffer src = genBuffer(); |
| |
| // write buffer completely to file |
| long position = 0L; |
| while (src.hasRemaining()) { |
| Future<Integer> result = ch.write(src, position); |
| try { |
| int n = result.get(); |
| // update position |
| position += n; |
| } catch (ExecutionException x) { |
| throw new RuntimeException(x.getCause()); |
| } catch (InterruptedException x) { |
| throw new RuntimeException(x); |
| } |
| } |
| |
| // read file into new buffer |
| ByteBuffer dst = (rand.nextBoolean()) ? |
| ByteBuffer.allocateDirect(src.capacity()) : |
| ByteBuffer.allocate(src.capacity()); |
| position = 0L; |
| int n; |
| do { |
| Future<Integer> result = ch.read(dst, position); |
| try { |
| n = result.get(); |
| |
| // update position |
| if (n > 0) position += n; |
| } catch (ExecutionException x) { |
| throw new RuntimeException(x.getCause()); |
| } catch (InterruptedException x) { |
| throw new RuntimeException(x); |
| } |
| } while (n > 0); |
| |
| // check buffers are the same |
| src.flip(); |
| dst.flip(); |
| if (!src.equals(dst)) { |
| throw new RuntimeException("Contents differ"); |
| } |
| } |
| |
| // exercise lock methods |
| static void testLocking(Path file) throws IOException { |
| System.out.println("testLocking"); |
| |
| AsynchronousFileChannel ch = AsynchronousFileChannel |
| .open(file, READ, WRITE); |
| FileLock fl; |
| try { |
| // test 1 - acquire lock and check that tryLock throws |
| // OverlappingFileLockException |
| try { |
| fl = ch.lock().get(); |
| } catch (ExecutionException x) { |
| throw new RuntimeException(x); |
| } catch (InterruptedException x) { |
| throw new RuntimeException("Should not be interrupted"); |
| } |
| if (!fl.acquiredBy().equals(ch)) |
| throw new RuntimeException("FileLock#acquiredBy returned incorrect channel"); |
| try { |
| ch.tryLock(); |
| throw new RuntimeException("OverlappingFileLockException expected"); |
| } catch (OverlappingFileLockException x) { |
| } |
| fl.release(); |
| |
| // test 2 - acquire try and check that lock throws OverlappingFileLockException |
| fl = ch.tryLock(); |
| if (fl == null) |
| throw new RuntimeException("Unable to acquire lock"); |
| try { |
| ch.lock((Void)null, new CompletionHandler<FileLock,Void> () { |
| public void completed(FileLock result, Void att) { |
| } |
| public void failed(Throwable exc, Void att) { |
| } |
| }); |
| throw new RuntimeException("OverlappingFileLockException expected"); |
| } catch (OverlappingFileLockException x) { |
| } |
| } finally { |
| ch.close(); |
| } |
| |
| // test 3 - channel is closed so FileLock should no longer be valid |
| if (fl.isValid()) |
| throw new RuntimeException("FileLock expected to be invalid"); |
| } |
| |
| // interrupt should not close channel |
| static void testInterruptHandlerThread(final AsynchronousFileChannel ch) { |
| System.out.println("testInterruptHandlerThread"); |
| |
| ByteBuffer buf = ByteBuffer.allocateDirect(100); |
| final CountDownLatch latch = new CountDownLatch(1); |
| |
| ch.read(buf, 0L, (Void)null, new CompletionHandler<Integer,Void>() { |
| public void completed(Integer result, Void att) { |
| try { |
| Thread.currentThread().interrupt(); |
| long size = ch.size(); |
| latch.countDown(); |
| } catch (IOException x) { |
| x.printStackTrace(); |
| } |
| } |
| public void failed(Throwable exc, Void att) { |
| } |
| }); |
| |
| // wait for handler to complete |
| await(latch); |
| } |
| |
| // invoke method on closed channel |
| static void testClosedChannel(AsynchronousFileChannel ch) { |
| System.out.println("testClosedChannel"); |
| |
| if (ch.isOpen()) |
| throw new RuntimeException("Channel should be closed"); |
| |
| ByteBuffer buf = ByteBuffer.allocateDirect(100); |
| |
| // check read fails with ClosedChannelException |
| try { |
| ch.read(buf, 0L).get(); |
| throw new RuntimeException("ExecutionException expected"); |
| } catch (ExecutionException x) { |
| if (!(x.getCause() instanceof ClosedChannelException)) |
| throw new RuntimeException("Cause of ClosedChannelException expected"); |
| } catch (InterruptedException x) { |
| } |
| |
| // check write fails with ClosedChannelException |
| try { |
| ch.write(buf, 0L).get(); |
| throw new RuntimeException("ExecutionException expected"); |
| } catch (ExecutionException x) { |
| if (!(x.getCause() instanceof ClosedChannelException)) |
| throw new RuntimeException("Cause of ClosedChannelException expected"); |
| } catch (InterruptedException x) { |
| } |
| |
| // check lock fails with ClosedChannelException |
| try { |
| ch.lock().get(); |
| throw new RuntimeException("ExecutionException expected"); |
| } catch (ExecutionException x) { |
| if (!(x.getCause() instanceof ClosedChannelException)) |
| throw new RuntimeException("Cause of ClosedChannelException expected"); |
| } catch (InterruptedException x) { |
| } |
| } |
| |
| |
| // exercise custom thread pool |
| static void testCustomThreadPool(Path file) throws IOException { |
| System.out.println("testCustomThreadPool"); |
| |
| // records threads that are created |
| final List<Thread> threads = new ArrayList<Thread>(); |
| |
| ThreadFactory threadFactory = new ThreadFactory() { |
| @Override |
| public Thread newThread(Runnable r) { |
| Thread t = new Thread(r); |
| t.setDaemon(true); |
| synchronized (threads) { |
| threads.add(t); |
| } |
| return t; |
| } |
| }; |
| |
| // exercise tests with varied number of threads |
| for (int nThreads=1; nThreads<=5; nThreads++) { |
| synchronized (threads) { |
| threads.clear(); |
| } |
| ExecutorService executor = Executors.newFixedThreadPool(nThreads, threadFactory); |
| Set<StandardOpenOption> opts = EnumSet.of(WRITE); |
| AsynchronousFileChannel ch = AsynchronousFileChannel.open(file, opts, executor); |
| try { |
| for (int i=0; i<10; i++) { |
| // do I/O operation to see which thread invokes the completion handler |
| final AtomicReference<Thread> invoker = new AtomicReference<Thread>(); |
| final CountDownLatch latch = new CountDownLatch(1); |
| |
| ch.write(genBuffer(), 0L, (Void)null, new CompletionHandler<Integer,Void>() { |
| public void completed(Integer result, Void att) { |
| invoker.set(Thread.currentThread()); |
| latch.countDown(); |
| } |
| public void failed(Throwable exc, Void att) { |
| } |
| }); |
| await(latch); |
| |
| // check invoker |
| boolean found = false; |
| synchronized (threads) { |
| for (Thread t: threads) { |
| if (t == invoker.get()) { |
| found = true; |
| break; |
| } |
| } |
| } |
| if (!found) |
| throw new RuntimeException("Invoker thread not found"); |
| } |
| } finally { |
| ch.close(); |
| executor.shutdown(); |
| } |
| } |
| |
| |
| // test sharing a thread pool between many channels |
| ExecutorService executor = Executors |
| .newFixedThreadPool(1+rand.nextInt(10), threadFactory); |
| final int n = 50 + rand.nextInt(50); |
| AsynchronousFileChannel[] channels = new AsynchronousFileChannel[n]; |
| try { |
| for (int i=0; i<n; i++) { |
| Set<StandardOpenOption> opts = EnumSet.of(WRITE); |
| channels[i] = AsynchronousFileChannel.open(file, opts, executor); |
| final CountDownLatch latch = new CountDownLatch(1); |
| channels[i].write(genBuffer(), 0L, (Void)null, new CompletionHandler<Integer,Void>() { |
| public void completed(Integer result, Void att) { |
| latch.countDown(); |
| } |
| public void failed(Throwable exc, Void att) { |
| } |
| }); |
| await(latch); |
| |
| // close ~half the channels |
| if (rand.nextBoolean()) |
| channels[i].close(); |
| } |
| } finally { |
| // close remaining channels |
| for (int i=0; i<n; i++) { |
| if (channels[i] != null) channels[i].close(); |
| } |
| executor.shutdown(); |
| } |
| } |
| |
| // exercise asynchronous close |
| static void testAsynchronousClose(Path file) throws IOException { |
| System.out.println("testAsynchronousClose"); |
| |
| // create file |
| AsynchronousFileChannel ch = AsynchronousFileChannel |
| .open(file, WRITE, TRUNCATE_EXISTING); |
| long size = 0L; |
| do { |
| ByteBuffer buf = genBuffer(); |
| int n = buf.remaining(); |
| writeFully(ch, buf, size); |
| size += n; |
| } while (size < (50L * 1024L * 1024L)); |
| |
| ch.close(); |
| |
| ch = AsynchronousFileChannel.open(file, WRITE, SYNC); |
| |
| // randomize number of writers, buffer size, and positions |
| |
| int nwriters = 1 + rand.nextInt(8); |
| ByteBuffer[] buf = new ByteBuffer[nwriters]; |
| long[] position = new long[nwriters]; |
| for (int i=0; i<nwriters; i++) { |
| buf[i] = genBuffer(); |
| position[i] = rand.nextInt((int)size); |
| } |
| |
| // initiate I/O |
| Future[] result = new Future[nwriters]; |
| for (int i=0; i<nwriters; i++) { |
| result[i] = ch.write(buf[i], position[i]); |
| } |
| |
| // close file |
| ch.close(); |
| |
| // write operations should complete or fail with AsynchronousCloseException |
| for (int i=0; i<nwriters; i++) { |
| try { |
| result[i].get(); |
| } catch (ExecutionException x) { |
| Throwable cause = x.getCause(); |
| if (!(cause instanceof AsynchronousCloseException)) |
| throw new RuntimeException(cause); |
| } catch (CancellationException x) { |
| throw new RuntimeException(x); // should not happen |
| } catch (InterruptedException x) { |
| throw new RuntimeException(x); // should not happen |
| } |
| } |
| } |
| |
| // exercise cancel method |
| static void testCancel(Path file) throws IOException { |
| System.out.println("testCancel"); |
| |
| for (int i=0; i<2; i++) { |
| boolean mayInterruptIfRunning = (i == 0) ? false : true; |
| |
| // open with SYNC option to improve chances that write will not |
| // complete immediately |
| AsynchronousFileChannel ch = AsynchronousFileChannel |
| .open(file, WRITE, SYNC); |
| |
| // start write operation |
| Future<Integer> res = ch.write(genBuffer(), 0L); |
| |
| // cancel operation |
| boolean cancelled = res.cancel(mayInterruptIfRunning); |
| |
| // check post-conditions |
| if (!res.isDone()) |
| throw new RuntimeException("isDone should return true"); |
| if (res.isCancelled() != cancelled) |
| throw new RuntimeException("isCancelled not consistent"); |
| try { |
| res.get(); |
| if (cancelled) |
| throw new RuntimeException("CancellationException expected"); |
| } catch (CancellationException x) { |
| if (!cancelled) |
| throw new RuntimeException("CancellationException not expected"); |
| } catch (ExecutionException x) { |
| throw new RuntimeException(x); |
| } catch (InterruptedException x) { |
| throw new RuntimeException(x); |
| } |
| try { |
| res.get(1, TimeUnit.SECONDS); |
| if (cancelled) |
| throw new RuntimeException("CancellationException expected"); |
| } catch (CancellationException x) { |
| if (!cancelled) |
| throw new RuntimeException("CancellationException not expected"); |
| } catch (ExecutionException x) { |
| throw new RuntimeException(x); |
| } catch (TimeoutException x) { |
| throw new RuntimeException(x); |
| } catch (InterruptedException x) { |
| throw new RuntimeException(x); |
| } |
| |
| ch.close(); |
| } |
| } |
| |
| // exercise truncate method |
| static void testTruncate(Path file) throws IOException { |
| System.out.println("testTruncate"); |
| |
| // basic tests |
| AsynchronousFileChannel ch = AsynchronousFileChannel |
| .open(file, CREATE, WRITE, TRUNCATE_EXISTING); |
| try { |
| writeFully(ch, genBuffer(), 0L); |
| long size = ch.size(); |
| |
| // attempt to truncate to a size greater than the current size |
| if (ch.truncate(size + 1L).size() != size) |
| throw new RuntimeException("Unexpected size after truncation"); |
| |
| // truncate file |
| if (ch.truncate(size - 1L).size() != (size - 1L)) |
| throw new RuntimeException("Unexpected size after truncation"); |
| |
| // invalid size |
| try { |
| ch.truncate(-1L); |
| throw new RuntimeException("IllegalArgumentException expected"); |
| } catch (IllegalArgumentException e) { } |
| |
| } finally { |
| ch.close(); |
| } |
| |
| // channel is closed |
| try { |
| ch.truncate(0L); |
| throw new RuntimeException("ClosedChannelException expected"); |
| } catch (ClosedChannelException e) { } |
| |
| // channel is read-only |
| ch = AsynchronousFileChannel.open(file, READ); |
| try { |
| try { |
| ch.truncate(0L); |
| throw new RuntimeException("NonWritableChannelException expected"); |
| } catch (NonWritableChannelException e) { } |
| } finally { |
| ch.close(); |
| } |
| } |
| |
| // returns ByteBuffer with random bytes |
| static ByteBuffer genBuffer() { |
| int size = 1024 + rand.nextInt(16000); |
| byte[] buf = new byte[size]; |
| boolean useDirect = rand.nextBoolean(); |
| if (useDirect) { |
| ByteBuffer bb = ByteBuffer.allocateDirect(buf.length); |
| bb.put(buf); |
| bb.flip(); |
| return bb; |
| } else { |
| return ByteBuffer.wrap(buf); |
| } |
| } |
| |
| // writes all remaining bytes in the buffer to the given channel at the |
| // given position |
| static void writeFully(final AsynchronousFileChannel ch, |
| final ByteBuffer src, |
| long position) |
| { |
| final CountDownLatch latch = new CountDownLatch(1); |
| |
| // use position as attachment |
| ch.write(src, position, position, new CompletionHandler<Integer,Long>() { |
| public void completed(Integer result, Long position) { |
| int n = result; |
| if (src.hasRemaining()) { |
| long p = position + n; |
| ch.write(src, p, p, this); |
| } else { |
| latch.countDown(); |
| } |
| } |
| public void failed(Throwable exc, Long position) { |
| } |
| }); |
| |
| // wait for writes to complete |
| await(latch); |
| } |
| |
| static void readAll(final AsynchronousFileChannel ch, |
| final ByteBuffer dst, |
| long position) |
| { |
| final CountDownLatch latch = new CountDownLatch(1); |
| |
| // use position as attachment |
| ch.read(dst, position, position, new CompletionHandler<Integer,Long>() { |
| public void completed(Integer result, Long position) { |
| int n = result; |
| if (n > 0) { |
| long p = position + n; |
| ch.read(dst, p, p, this); |
| } else { |
| latch.countDown(); |
| } |
| } |
| public void failed(Throwable exc, Long position) { |
| } |
| }); |
| |
| // wait for reads to complete |
| await(latch); |
| } |
| |
| static void await(CountDownLatch latch) { |
| // wait until done |
| boolean done = false; |
| while (!done) { |
| try { |
| latch.await(); |
| done = true; |
| } catch (InterruptedException x) { } |
| } |
| } |
| } |