blob: 39a67ffe1c31c3aa96301025d3b3ad166a14991d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.lucene.store;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException; // javadoc @link
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future; // javadoc
import org.apache.lucene.util.IOUtils;
/**
* An {@link FSDirectory} implementation that uses java.nio's FileChannel's positional read, which
* allows multiple threads to read from the same file without synchronizing.
*
* <p>This class only uses FileChannel when reading; writing is achieved with {@link
* FSDirectory.FSIndexOutput}.
*
* <p><b>NOTE</b>: NIOFSDirectory is not recommended on Windows because of a bug in how
* FileChannel.read is implemented in Sun's JRE. Inside of the implementation the position is
* apparently synchronized. See <a
* href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6265734">here</a> for details.
*
* <p><b>NOTE:</b> Accessing this class either directly or indirectly from a thread while it's
* interrupted can close the underlying file descriptor immediately if at the same time the thread
* is blocked on IO. The file descriptor will remain closed and subsequent access to {@link
* NIOFSDirectory} will throw a {@link ClosedChannelException}. If your application uses either
* {@link Thread#interrupt()} or {@link Future#cancel(boolean)} you should use the legacy {@code
* RAFDirectory} from the Lucene {@code misc} module in favor of {@link NIOFSDirectory}.
*/
public class NIOFSDirectory extends FSDirectory {
/**
* Create a new NIOFSDirectory for the named location. The directory is created at the named
* location if it does not yet exist.
*
* @param path the path of the directory
* @param lockFactory the lock factory to use
* @throws IOException if there is a low-level I/O error
*/
public NIOFSDirectory(Path path, LockFactory lockFactory) throws IOException {
super(path, lockFactory);
}
/**
* Create a new NIOFSDirectory for the named location and {@link FSLockFactory#getDefault()}. The
* directory is created at the named location if it does not yet exist.
*
* @param path the path of the directory
* @throws IOException if there is a low-level I/O error
*/
public NIOFSDirectory(Path path) throws IOException {
this(path, FSLockFactory.getDefault());
}
@Override
public IndexInput openInput(String name, IOContext context) throws IOException {
ensureOpen();
ensureCanRead(name);
Path path = getDirectory().resolve(name);
FileChannel fc = FileChannel.open(path, StandardOpenOption.READ);
boolean success = false;
try {
final NIOFSIndexInput indexInput =
new NIOFSIndexInput("NIOFSIndexInput(path=\"" + path + "\")", fc, context);
success = true;
return indexInput;
} finally {
if (success == false) {
IOUtils.closeWhileHandlingException(fc);
}
}
}
/** Reads bytes with {@link FileChannel#read(ByteBuffer, long)} */
static final class NIOFSIndexInput extends BufferedIndexInput {
/** The maximum chunk size for reads of 16384 bytes. */
private static final int CHUNK_SIZE = 16384;
/** the file channel we will read from */
protected final FileChannel channel;
/** is this instance a clone and hence does not own the file to close it */
boolean isClone = false;
/** start offset: non-zero in the slice case */
protected final long off;
/** end offset (start+length) */
protected final long end;
public NIOFSIndexInput(String resourceDesc, FileChannel fc, IOContext context)
throws IOException {
super(resourceDesc, context);
this.channel = fc;
this.off = 0L;
this.end = fc.size();
}
public NIOFSIndexInput(
String resourceDesc, FileChannel fc, long off, long length, int bufferSize) {
super(resourceDesc, bufferSize);
this.channel = fc;
this.off = off;
this.end = off + length;
this.isClone = true;
}
@Override
public void close() throws IOException {
if (!isClone) {
channel.close();
}
}
@Override
public NIOFSIndexInput clone() {
NIOFSIndexInput clone = (NIOFSIndexInput) super.clone();
clone.isClone = true;
return clone;
}
@Override
public IndexInput slice(String sliceDescription, long offset, long length) throws IOException {
if (offset < 0 || length < 0 || offset + length > this.length()) {
throw new IllegalArgumentException(
"slice() "
+ sliceDescription
+ " out of bounds: offset="
+ offset
+ ",length="
+ length
+ ",fileLength="
+ this.length()
+ ": "
+ this);
}
return new NIOFSIndexInput(
getFullSliceDescription(sliceDescription),
channel,
off + offset,
length,
getBufferSize());
}
@Override
public final long length() {
return end - off;
}
@Override
protected void readInternal(ByteBuffer b) throws IOException {
long pos = getFilePointer() + off;
if (pos + b.remaining() > end) {
throw new EOFException("read past EOF: " + this);
}
try {
int readLength = b.remaining();
while (readLength > 0) {
final int toRead = Math.min(CHUNK_SIZE, readLength);
b.limit(b.position() + toRead);
assert b.remaining() == toRead;
final int i = channel.read(b, pos);
if (i < 0) {
// be defensive here, even though we checked before hand, something could have changed
throw new EOFException(
"read past EOF: "
+ this
+ " buffer: "
+ b
+ " chunkLen: "
+ toRead
+ " end: "
+ end);
}
assert i > 0
: "FileChannel.read with non zero-length bb.remaining() must always read at least "
+ "one byte (FileChannel is in blocking mode, see spec of ReadableByteChannel)";
pos += i;
readLength -= i;
}
assert readLength == 0;
} catch (IOException ioe) {
throw new IOException(ioe.getMessage() + ": " + this, ioe);
}
}
@Override
protected void seekInternal(long pos) throws IOException {
if (pos > length()) {
throw new EOFException(
"read past EOF: pos=" + pos + " vs length=" + length() + ": " + this);
}
}
}
}