/* * 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 */ 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) { } } } }