blob: c525ef1fe849df5f1bd656750434b72c4196096f [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.jimfs;
import static com.google.common.jimfs.TestUtils.assertNotEquals;
import static com.google.common.jimfs.TestUtils.buffer;
import static com.google.common.jimfs.TestUtils.bytes;
import static com.google.common.jimfs.TestUtils.regularFile;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableSet;
import com.google.common.testing.NullPointerTester;
import com.google.common.util.concurrent.Runnables;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.FileLockInterruptionException;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.file.OpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Most of the behavior of {@link JimfsFileChannel} is handled by the {@link RegularFile}
* implementations, so the thorough tests of that are in {@link RegularFileTest}. This mostly tests
* interactions with the file and channel positions.
*
* @author Colin Decker
*/
@RunWith(JUnit4.class)
public class JimfsFileChannelTest {
private static FileChannel channel(RegularFile file, OpenOption... options) throws IOException {
return new JimfsFileChannel(
file,
Options.getOptionsForChannel(ImmutableSet.copyOf(options)),
new FileSystemState(Runnables.doNothing()));
}
@Test
public void testPosition() throws IOException {
FileChannel channel = channel(regularFile(10), READ);
assertEquals(0, channel.position());
assertSame(channel, channel.position(100));
assertEquals(100, channel.position());
}
@Test
public void testSize() throws IOException {
RegularFile file = regularFile(10);
FileChannel channel = channel(file, READ);
assertEquals(10, channel.size());
file.write(10, new byte[90], 0, 90);
assertEquals(100, channel.size());
}
@Test
public void testRead() throws IOException {
RegularFile file = regularFile(20);
FileChannel channel = channel(file, READ);
assertEquals(0, channel.position());
ByteBuffer buf = buffer("1234567890");
ByteBuffer buf2 = buffer("123457890");
assertEquals(10, channel.read(buf));
assertEquals(10, channel.position());
buf.flip();
assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2}));
assertEquals(20, channel.position());
buf.flip();
buf2.flip();
file.write(20, new byte[10], 0, 10);
assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2}, 0, 2));
assertEquals(30, channel.position());
buf.flip();
assertEquals(10, channel.read(buf, 5));
assertEquals(30, channel.position());
buf.flip();
assertEquals(-1, channel.read(buf));
assertEquals(30, channel.position());
}
@Test
public void testWrite() throws IOException {
RegularFile file = regularFile(0);
FileChannel channel = channel(file, WRITE);
assertEquals(0, channel.position());
ByteBuffer buf = buffer("1234567890");
ByteBuffer buf2 = buffer("1234567890");
assertEquals(10, channel.write(buf));
assertEquals(10, channel.position());
buf.flip();
assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}));
assertEquals(30, channel.position());
buf.flip();
buf2.flip();
assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2));
assertEquals(50, channel.position());
buf.flip();
assertEquals(10, channel.write(buf, 5));
assertEquals(50, channel.position());
}
@Test
public void testAppend() throws IOException {
RegularFile file = regularFile(0);
FileChannel channel = channel(file, WRITE, APPEND);
assertEquals(0, channel.position());
ByteBuffer buf = buffer("1234567890");
ByteBuffer buf2 = buffer("1234567890");
assertEquals(10, channel.write(buf));
assertEquals(10, channel.position());
buf.flip();
channel.position(0);
assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}));
assertEquals(30, channel.position());
buf.flip();
buf2.flip();
channel.position(0);
assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2));
assertEquals(50, channel.position());
buf.flip();
channel.position(0);
assertEquals(10, channel.write(buf, 5));
assertEquals(60, channel.position());
buf.flip();
channel.position(0);
assertEquals(10, channel.transferFrom(new ByteBufferChannel(buf), 0, 10));
assertEquals(70, channel.position());
}
@Test
public void testTransferTo() throws IOException {
RegularFile file = regularFile(10);
FileChannel channel = channel(file, READ);
ByteBufferChannel writeChannel = new ByteBufferChannel(buffer("1234567890"));
assertEquals(10, channel.transferTo(0, 100, writeChannel));
assertEquals(0, channel.position());
}
@Test
public void testTransferFrom() throws IOException {
RegularFile file = regularFile(0);
FileChannel channel = channel(file, WRITE);
ByteBufferChannel readChannel = new ByteBufferChannel(buffer("1234567890"));
assertEquals(10, channel.transferFrom(readChannel, 0, 100));
assertEquals(0, channel.position());
}
@Test
public void testTruncate() throws IOException {
RegularFile file = regularFile(10);
FileChannel channel = channel(file, WRITE);
channel.truncate(10); // no resize, >= size
assertEquals(10, file.size());
channel.truncate(11); // no resize, > size
assertEquals(10, file.size());
channel.truncate(5); // resize down to 5
assertEquals(5, file.size());
channel.position(20);
channel.truncate(10);
assertEquals(10, channel.position());
channel.truncate(2);
assertEquals(2, channel.position());
}
@Test
public void testFileTimeUpdates() throws IOException {
RegularFile file = regularFile(10);
FileChannel channel =
new JimfsFileChannel(
file,
ImmutableSet.<OpenOption>of(READ, WRITE),
new FileSystemState(Runnables.doNothing()));
// accessed
long accessTime = file.getLastAccessTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.read(ByteBuffer.allocate(10));
assertNotEquals(accessTime, file.getLastAccessTime());
accessTime = file.getLastAccessTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.read(ByteBuffer.allocate(10), 0);
assertNotEquals(accessTime, file.getLastAccessTime());
accessTime = file.getLastAccessTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)});
assertNotEquals(accessTime, file.getLastAccessTime());
accessTime = file.getLastAccessTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1);
assertNotEquals(accessTime, file.getLastAccessTime());
accessTime = file.getLastAccessTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.transferTo(0, 10, new ByteBufferChannel(10));
assertNotEquals(accessTime, file.getLastAccessTime());
// modified
long modifiedTime = file.getLastModifiedTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.write(ByteBuffer.allocate(10));
assertNotEquals(modifiedTime, file.getLastModifiedTime());
modifiedTime = file.getLastModifiedTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.write(ByteBuffer.allocate(10), 0);
assertNotEquals(modifiedTime, file.getLastModifiedTime());
modifiedTime = file.getLastModifiedTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)});
assertNotEquals(modifiedTime, file.getLastModifiedTime());
modifiedTime = file.getLastModifiedTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1);
assertNotEquals(modifiedTime, file.getLastModifiedTime());
modifiedTime = file.getLastModifiedTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.truncate(0);
assertNotEquals(modifiedTime, file.getLastModifiedTime());
modifiedTime = file.getLastModifiedTime();
Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
channel.transferFrom(new ByteBufferChannel(10), 0, 10);
assertNotEquals(modifiedTime, file.getLastModifiedTime());
}
@Test
public void testClose() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
ExecutorService executor = Executors.newSingleThreadExecutor();
assertTrue(channel.isOpen());
channel.close();
assertFalse(channel.isOpen());
try {
channel.position();
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.position(0);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.lock();
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.lock(0, 10, true);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.tryLock();
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.tryLock(0, 10, true);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.force(true);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.write(buffer("111"));
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.write(buffer("111"), 10);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.write(new ByteBuffer[] {buffer("111"), buffer("111")});
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.truncate(0);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.read(buffer("111"));
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.read(buffer("111"), 10);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.read(new ByteBuffer[] {buffer("111"), buffer("111")});
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
fail();
} catch (ClosedChannelException expected) {
}
try {
channel.transferTo(0, 10, new ByteBufferChannel(buffer("111")));
fail();
} catch (ClosedChannelException expected) {
}
executor.shutdown();
}
@Test
public void testWritesInReadOnlyMode() throws IOException {
FileChannel channel = channel(regularFile(0), READ);
try {
channel.write(buffer("111"));
fail();
} catch (NonWritableChannelException expected) {
}
try {
channel.write(buffer("111"), 10);
fail();
} catch (NonWritableChannelException expected) {
}
try {
channel.write(new ByteBuffer[] {buffer("111"), buffer("111")});
fail();
} catch (NonWritableChannelException expected) {
}
try {
channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
fail();
} catch (NonWritableChannelException expected) {
}
try {
channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4);
fail();
} catch (NonWritableChannelException expected) {
}
try {
channel.truncate(0);
fail();
} catch (NonWritableChannelException expected) {
}
try {
channel.lock(0, 10, false);
fail();
} catch (NonWritableChannelException expected) {
}
}
@Test
public void testReadsInWriteOnlyMode() throws IOException {
FileChannel channel = channel(regularFile(0), WRITE);
try {
channel.read(buffer("111"));
fail();
} catch (NonReadableChannelException expected) {
}
try {
channel.read(buffer("111"), 10);
fail();
} catch (NonReadableChannelException expected) {
}
try {
channel.read(new ByteBuffer[] {buffer("111"), buffer("111")});
fail();
} catch (NonReadableChannelException expected) {
}
try {
channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
fail();
} catch (NonReadableChannelException expected) {
}
try {
channel.transferTo(0, 10, new ByteBufferChannel(buffer("111")));
fail();
} catch (NonReadableChannelException expected) {
}
try {
channel.lock(0, 10, true);
fail();
} catch (NonReadableChannelException expected) {
}
}
@Test
public void testPositionNegative() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
try {
channel.position(-1);
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testTruncateNegative() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
try {
channel.truncate(-1);
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testWriteNegative() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
try {
channel.write(buffer("111"), -1);
fail();
} catch (IllegalArgumentException expected) {
}
ByteBuffer[] bufs = {buffer("111"), buffer("111")};
try {
channel.write(bufs, -1, 10);
fail();
} catch (IndexOutOfBoundsException expected) {
}
try {
channel.write(bufs, 0, -1);
fail();
} catch (IndexOutOfBoundsException expected) {
}
}
@Test
public void testReadNegative() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
try {
channel.read(buffer("111"), -1);
fail();
} catch (IllegalArgumentException expected) {
}
ByteBuffer[] bufs = {buffer("111"), buffer("111")};
try {
channel.read(bufs, -1, 10);
fail();
} catch (IndexOutOfBoundsException expected) {
}
try {
channel.read(bufs, 0, -1);
fail();
} catch (IndexOutOfBoundsException expected) {
}
}
@Test
public void testTransferToNegative() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
try {
channel.transferTo(-1, 0, new ByteBufferChannel(10));
fail();
} catch (IllegalArgumentException expected) {
}
try {
channel.transferTo(0, -1, new ByteBufferChannel(10));
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testTransferFromNegative() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
try {
channel.transferFrom(new ByteBufferChannel(10), -1, 0);
fail();
} catch (IllegalArgumentException expected) {
}
try {
channel.transferFrom(new ByteBufferChannel(10), 0, -1);
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testLockNegative() throws IOException {
FileChannel channel = channel(regularFile(0), READ, WRITE);
try {
channel.lock(-1, 10, true);
fail();
} catch (IllegalArgumentException expected) {
}
try {
channel.lock(0, -1, true);
fail();
} catch (IllegalArgumentException expected) {
}
try {
channel.tryLock(-1, 10, true);
fail();
} catch (IllegalArgumentException expected) {
}
try {
channel.tryLock(0, -1, true);
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testNullPointerExceptions() throws IOException {
FileChannel channel = channel(regularFile(100), READ, WRITE);
NullPointerTester tester = new NullPointerTester();
tester.testAllPublicInstanceMethods(channel);
}
@Test
public void testLock() throws IOException {
FileChannel channel = channel(regularFile(10), READ, WRITE);
assertNotNull(channel.lock());
assertNotNull(channel.lock(0, 10, false));
assertNotNull(channel.lock(0, 10, true));
assertNotNull(channel.tryLock());
assertNotNull(channel.tryLock(0, 10, false));
assertNotNull(channel.tryLock(0, 10, true));
FileLock lock = channel.lock();
assertTrue(lock.isValid());
lock.release();
assertFalse(lock.isValid());
}
@Test
public void testAsynchronousClose() throws Exception {
RegularFile file = regularFile(10);
final FileChannel channel = channel(file, READ, WRITE);
file.writeLock().lock(); // ensure all operations on the channel will block
ExecutorService executor = Executors.newCachedThreadPool();
CountDownLatch latch = new CountDownLatch(BLOCKING_OP_COUNT);
List<Future<?>> futures = queueAllBlockingOperations(channel, executor, latch);
// wait for all the threads to have started running
latch.await();
// then ensure time for operations to start blocking
Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS);
// close channel on this thread
channel.close();
// the blocking operations are running on different threads, so they all get
// AsynchronousCloseException
for (Future<?> future : futures) {
try {
future.get();
fail();
} catch (ExecutionException expected) {
assertWithMessage("blocking thread exception")
.that(expected.getCause())
.isInstanceOf(AsynchronousCloseException.class);
}
}
}
@Test
public void testCloseByInterrupt() throws Exception {
RegularFile file = regularFile(10);
final FileChannel channel = channel(file, READ, WRITE);
file.writeLock().lock(); // ensure all operations on the channel will block
ExecutorService executor = Executors.newCachedThreadPool();
final CountDownLatch threadStartLatch = new CountDownLatch(1);
final SettableFuture<Throwable> interruptException = SettableFuture.create();
// This thread, being the first to run, will be blocking on the interruptible lock (the byte
// file's write lock) and as such will be interrupted properly... the other threads will be
// blocked on the lock that guards the position field and the specification that only one method
// on the channel will be in progress at a time. That lock is not interruptible, so we must
// interrupt this thread.
Thread thread =
new Thread(
new Runnable() {
@Override
public void run() {
threadStartLatch.countDown();
try {
channel.write(ByteBuffer.allocate(20));
interruptException.set(null);
} catch (Throwable e) {
interruptException.set(e);
}
}
});
thread.start();
// let the thread start running
threadStartLatch.await();
// then ensure time for thread to start blocking on the write lock
Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS);
CountDownLatch blockingStartLatch = new CountDownLatch(BLOCKING_OP_COUNT);
List<Future<?>> futures = queueAllBlockingOperations(channel, executor, blockingStartLatch);
// wait for all blocking threads to start
blockingStartLatch.await();
// then ensure time for the operations to start blocking
Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS);
// interrupting this blocking thread closes the channel and makes all the other threads
// throw AsynchronousCloseException... the operation on this thread should throw
// ClosedByInterruptException
thread.interrupt();
// get the exception that caused the interrupted operation to terminate
assertWithMessage("interrupted thread exception")
.that(interruptException.get(200, MILLISECONDS))
.isInstanceOf(ClosedByInterruptException.class);
// check that each other thread got AsynchronousCloseException (since the interrupt, on a
// different thread, closed the channel)
for (Future<?> future : futures) {
try {
future.get();
fail();
} catch (ExecutionException expected) {
assertWithMessage("blocking thread exception")
.that(expected.getCause())
.isInstanceOf(AsynchronousCloseException.class);
}
}
}
private static final int BLOCKING_OP_COUNT = 10;
/**
* Queues blocking operations on the channel in separate threads using the given executor. The
* given latch should have a count of BLOCKING_OP_COUNT to allow the caller wants to wait for all
* threads to start executing.
*/
private List<Future<?>> queueAllBlockingOperations(
final FileChannel channel, ExecutorService executor, final CountDownLatch startLatch) {
List<Future<?>> futures = new ArrayList<>();
final ByteBuffer buffer = ByteBuffer.allocate(10);
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.write(buffer);
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.write(buffer, 0);
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.write(new ByteBuffer[] {buffer, buffer});
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.write(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2);
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.read(buffer);
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.read(buffer, 0);
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.read(new ByteBuffer[] {buffer, buffer});
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.read(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2);
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.transferTo(0, 10, new ByteBufferChannel(buffer));
return null;
}
}));
futures.add(
executor.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
startLatch.countDown();
channel.transferFrom(new ByteBufferChannel(buffer), 0, 10);
return null;
}
}));
return futures;
}
/**
* Tests that the methods on the default FileChannel that support InterruptibleChannel behavior
* also support it on JimfsFileChannel, by just interrupting the thread before calling the method.
*/
@Test
public void testInterruptedThreads() throws IOException {
final ByteBuffer buf = ByteBuffer.allocate(10);
final ByteBuffer[] bufArray = {buf};
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.size();
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.position();
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.position(0);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.write(buf);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.write(bufArray, 0, 1);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.read(buf);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.read(bufArray, 0, 1);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.write(buf, 0);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.read(buf, 0);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.transferTo(0, 1, channel(regularFile(10), READ, WRITE));
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.transferFrom(channel(regularFile(10), READ, WRITE), 0, 1);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.force(true);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.truncate(0);
}
});
assertClosedByInterrupt(
new FileChannelMethod() {
@Override
public void call(FileChannel channel) throws IOException {
channel.lock(0, 1, true);
}
});
// tryLock() does not handle interruption
// map() always throws UOE; it doesn't make sense for it to try to handle interruption
}
private interface FileChannelMethod {
void call(FileChannel channel) throws IOException;
}
/**
* Asserts that when the given operation is run on an interrupted thread, {@code
* ClosedByInterruptException} is thrown, the channel is closed and the thread is no longer
* interrupted.
*/
private static void assertClosedByInterrupt(FileChannelMethod method) throws IOException {
FileChannel channel = channel(regularFile(10), READ, WRITE);
Thread.currentThread().interrupt();
try {
method.call(channel);
fail(
"expected the method to throw ClosedByInterruptException or "
+ "FileLockInterruptionException");
} catch (ClosedByInterruptException | FileLockInterruptionException expected) {
assertFalse("expected the channel to be closed", channel.isOpen());
assertTrue("expected the thread to still be interrupted", Thread.interrupted());
} finally {
Thread.interrupted(); // ensure the thread isn't interrupted when this method returns
}
}
}