/*
* 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
An FSDirectory
implementation that uses java.nio's FileChannel's positional read, which allows multiple threads to read from the same file without synchronizing. This class only uses FileChannel when reading; writing is achieved with FSIndexOutput
.
NOTE: 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 here
for details.
NOTE: 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 NIOFSDirectory
will throw a ClosedChannelException
. If your application uses either Thread.interrupt()
or Future.cancel(boolean)
you should use the legacy RAFDirectory
from the Lucene misc
module in favor of NIOFSDirectory
.
/**
* 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>
* <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}.
* </p>
*/
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.
Params: - path – the path of the directory
- lockFactory – the lock factory to use
Throws: - IOException – if there is a low-level I/O error
/** 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 FSLockFactory.getDefault()
. The directory is created at the named location if it does not yet exist. Params: - path – the path of the directory
Throws: - IOException – if there is a low-level I/O error
/** 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);
return new NIOFSIndexInput("NIOFSIndexInput(path=\"" + path + "\")", fc, context);
}
Reads bytes with FileChannel.read(ByteBuffer, long)
/**
* Reads bytes with {@link FileChannel#read(ByteBuffer, long)}
*/
static final class NIOFSIndexInput extends BufferedIndexInput {
The maximum chunk size for reads of 16384 bytes.
/**
* The maximum chunk size for reads of 16384 bytes.
*/
private static final int CHUNK_SIZE = 16384;
the file channel we will read from /** 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 /** 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 /** start offset: non-zero in the slice case */
protected final long off;
end offset (start+length) /** end offset (start+length) */
protected final long end;
private ByteBuffer byteBuf; // wraps the buffer for NIO
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 newBuffer(byte[] newBuffer) {
super.newBuffer(newBuffer);
byteBuf = ByteBuffer.wrap(newBuffer);
}
@Override
protected void readInternal(byte[] b, int offset, int len) throws IOException {
final ByteBuffer bb;
// Determine the ByteBuffer we should use
if (b == buffer) {
// Use our own pre-wrapped byteBuf:
assert byteBuf != null;
bb = byteBuf;
byteBuf.clear().position(offset);
} else {
bb = ByteBuffer.wrap(b, offset, len);
}
long pos = getFilePointer() + off;
if (pos + len > end) {
throw new EOFException("read past EOF: " + this);
}
try {
int readLength = len;
while (readLength > 0) {
final int toRead = Math.min(CHUNK_SIZE, readLength);
bb.limit(bb.position() + toRead);
assert bb.remaining() == toRead;
final int i = channel.read(bb, pos);
if (i < 0) { // be defensive here, even though we checked before hand, something could have changed
throw new EOFException("read past EOF: " + this + " off: " + offset + " len: " + len + " pos: " + pos + " 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);
}
}
}
}