/**
* Copyright 2012 Akiban Technologies, 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.persistit;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import com.persistit.MediatedFileChannel.TestChannelInjector;
/**
* <p>
* A {@link FileChannel} implementation that simulates IOExceptions under
* control of a unit test program. This class implements only those methods used
* by Persistit; many methods of FileChannel throw
* {@link UnsupportedOperationException}.
* </p>
*
* @author peter
*
*/
class ErrorInjectingFileChannel extends FileChannel implements TestChannelInjector {
volatile FileChannel _channel;
volatile IOException _injectedIOException;
volatile String _injectedIOExceptionFlags;
volatile long _injectedDiskFullLimit = Long.MAX_VALUE;
@Override
public void setChannel(final FileChannel channel) {
_channel = channel;
}
private void injectFailure(final char type) throws IOException {
final IOException e = _injectedIOException;
if (e != null && _injectedIOExceptionFlags.indexOf(type) >= 0) {
throw e;
}
}
/**
* Set an IOException to be thrown on subsequent I/O operations. This method
* is intended for use only for unit tests. The <code>flags</code> parameter
* determines which I/O operations throw exceptions:
* <ul>
* <li>o - open</li>
* <li>c - close</li>
* <li>r - read</li>
* <li>w - write</li>
* <li>f - force</li>
* <li>t - truncate</li>
* <li>l - lock</li>
* <li>e - extending Volume file</li>
* </ul>
* For example, if flags is "wt" then write and truncate operations with
* throw the injected IOException.
*
* @param exception
* The IOException to throw
* @param flags
* Selected operations
*/
void injectTestIOException(final IOException exception, final String flags) {
_injectedIOException = exception;
_injectedIOExceptionFlags = flags;
}
/**
* Sets a file position at which writes will simulate a disk-full condition
* by throwing an IOException.
*
* @param limit
*/
void injectDiskFullLimit(final long limit) {
_injectedDiskFullLimit = limit;
}
@Override
protected void implCloseChannel() throws IOException {
_channel.close();
injectFailure('c');
}
/*
* --------------------------------
*
* Implementations of these FileChannel methods simply delegate to the inner
* FileChannel. But they retry upon receiving a ClosedChannelException
* caused by an I/O operation on a different thread having been interrupted.
*
* --------------------------------
*/
@Override
public void force(final boolean metaData) throws IOException {
injectFailure('f');
_channel.force(metaData);
}
@Override
public int read(final ByteBuffer byteBuffer, final long position) throws IOException {
injectFailure('r');
return _channel.read(byteBuffer, position);
}
@Override
public long size() throws IOException {
injectFailure('s');
return _channel.size();
}
@Override
public FileChannel truncate(final long size) throws IOException {
injectFailure('t');
return _channel.truncate(size);
}
@Override
public synchronized FileLock tryLock(final long position, final long size, final boolean shared) throws IOException {
injectFailure('l');
return _channel.tryLock(position, size, shared);
}
@Override
public int write(final ByteBuffer byteBuffer, final long position) throws IOException {
injectFailure('w');
if (byteBuffer.remaining() == 1) {
injectFailure('e');
}
final long capacity = Math.max(0L, _injectedDiskFullLimit - position);
final int delta = (int) Math.max(0L, byteBuffer.remaining() - capacity);
byteBuffer.limit(byteBuffer.limit() - delta);
final int written = _channel.write(byteBuffer, position);
byteBuffer.limit(byteBuffer.limit() + delta);
/*
* Modified to mimic behavior seen on an actual full disk: A Disk Full
* condition is thrown only if no bytes can be written.
*/
if (delta > 0 && written == 0) {
throw new IOException("Disk Full");
} else {
return written;
}
}
/*
* --------------------------------
*
* Persistit does not use these methods and so they are Unsupported. Note
* that it would be difficult to support the relative read/write methods
* because the channel size is unavailable after it is closed. Therefore a
* client of this class must maintain its own position counter and cannot
* use the relative-addressing calls.
*
* --------------------------------
*/
@Override
public FileLock lock(final long position, final long size, final boolean shared) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public MappedByteBuffer map(final MapMode arg0, final long arg1, final long arg2) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long position() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public FileChannel position(final long arg0) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int read(final ByteBuffer byteBuffer) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long read(final ByteBuffer[] arg0, final int arg1, final int arg2) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long transferFrom(final ReadableByteChannel arg0, final long arg1, final long arg2) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long transferTo(final long arg0, final long arg1, final WritableByteChannel arg2) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int write(final ByteBuffer byteBuffer) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long write(final ByteBuffer[] arg0, final int arg1, final int arg2) throws IOException {
throw new UnsupportedOperationException();
}
}