| using System; |
| using System.Diagnostics; |
| |
| namespace org.apache.lucene.store |
| { |
| |
| /* |
| * 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. |
| */ |
| |
| ignore |
| import java.nio.channels.FileChannel; |
| |
| import org.apache.lucene.store.Directory; // javadoc |
| import org.apache.lucene.store.IOContext.Context; |
| |
| // TODO |
| // - newer Linux kernel versions (after 2.6.29) have |
| // improved MADV_SEQUENTIAL (and hopefully also |
| // FADV_SEQUENTIAL) interaction with the buffer |
| // cache; we should explore using that instead of direct |
| // IO when context is merge |
| |
| /// <summary> |
| /// A <seealso cref="Directory"/> implementation for all Unixes that uses |
| /// DIRECT I/O to bypass OS level IO caching during |
| /// merging. For all other cases (searching, writing) we delegate |
| /// to the provided Directory instance. |
| /// |
| /// <para>See <a |
| /// href="{@docRoot}/overview-summary.html#NativeUnixDirectory">Overview</a> |
| /// for more details. |
| /// |
| /// </para> |
| /// <para>To use this you must compile |
| /// NativePosixUtil.cpp (exposes Linux-specific APIs through |
| /// JNI) for your platform, by running <code>ant |
| /// build-native-unix</code>, and then putting the resulting |
| /// <code>libNativePosixUtil.so</code> (from |
| /// <code>lucene/build/native</code>) onto your dynamic |
| /// linker search path. |
| /// |
| /// </para> |
| /// <para><b>WARNING</b>: this code is very new and quite easily |
| /// could contain horrible bugs. For example, here's one |
| /// known issue: if you use seek in <code>IndexOutput</code>, and then |
| /// write more than one buffer's worth of bytes, then the |
| /// file will be wrong. Lucene does not do this today (only writes |
| /// small number of bytes after seek), but that may change. |
| /// |
| /// </para> |
| /// <para>This directory passes Solr and Lucene tests on Linux |
| /// and OS X; other Unixes should work but have not been |
| /// tested! Use at your own risk. |
| /// |
| /// @lucene.experimental |
| /// </para> |
| /// </summary> |
| public class NativeUnixDirectory extends FSDirectory |
| { |
| |
| // TODO: this is OS dependent, but likely 512 is the LCD |
| private final static long ALIGN = 512; |
| private final static long ALIGN_NOT_MASK = ~(ALIGN - 1); |
| |
| /// <summary> |
| /// Default buffer size before writing to disk (256 KB); |
| /// larger means less IO load but more RAM and direct |
| /// buffer storage space consumed during merging. |
| /// </summary> |
| |
| public final static int DEFAULT_MERGE_BUFFER_SIZE = 262144; |
| |
| /// <summary> |
| /// Default min expected merge size before direct IO is |
| /// used (10 MB): |
| /// </summary> |
| public final static long DEFAULT_MIN_BYTES_DIRECT = 10 * 1024 * 1024; |
| |
| private final int mergeBufferSize; |
| private final long minBytesDirect; |
| private final Directory @delegate; |
| |
| /// <summary> |
| /// Create a new NIOFSDirectory for the named location. |
| /// </summary> |
| /// <param name="path"> the path of the directory </param> |
| /// <param name="mergeBufferSize"> Size of buffer to use for |
| /// merging. See <seealso cref="#DEFAULT_MERGE_BUFFER_SIZE"/>. </param> |
| /// <param name="minBytesDirect"> Merges, or files to be opened for |
| /// reading, smaller than this will |
| /// not use direct IO. See {@link |
| /// #DEFAULT_MIN_BYTES_DIRECT} </param> |
| /// <param name="delegate"> fallback Directory for non-merges </param> |
| /// <exception cref="IOException"> If there is a low-level I/O error </exception> |
| public NativeUnixDirectory(File path, int mergeBufferSize, long minBytesDirect, Directory @delegate) throws IOException |
| { |
| base(path, @delegate.LockFactory); |
| if ((mergeBufferSize & ALIGN) != 0) |
| { |
| throw new System.ArgumentException("mergeBufferSize must be 0 mod " + ALIGN + " (got: " + mergeBufferSize + ")"); |
| } |
| this.mergeBufferSize = mergeBufferSize; |
| this.minBytesDirect = minBytesDirect; |
| this.@delegate = @delegate; |
| } |
| |
| /// <summary> |
| /// Create a new NIOFSDirectory for the named location. |
| /// </summary> |
| /// <param name="path"> the path of the directory </param> |
| /// <param name="delegate"> fallback Directory for non-merges </param> |
| /// <exception cref="IOException"> If there is a low-level I/O error </exception> |
| public NativeUnixDirectory(File path, Directory @delegate) throws IOException |
| { |
| this(path, DEFAULT_MERGE_BUFFER_SIZE, DEFAULT_MIN_BYTES_DIRECT, @delegate); |
| } |
| |
| public IndexInput openInput(string name, IOContext context) throws IOException |
| { |
| ensureOpen(); |
| if (context.context != Context.MERGE || context.mergeInfo.estimatedMergeBytes < minBytesDirect || fileLength(name) < minBytesDirect) |
| { |
| return @delegate.openInput(name, context); |
| } |
| else |
| { |
| return new NativeUnixIndexInput(new File(Directory, name), mergeBufferSize); |
| } |
| } |
| |
| public IndexOutput createOutput(string name, IOContext context) throws IOException |
| { |
| ensureOpen(); |
| if (context.context != Context.MERGE || context.mergeInfo.estimatedMergeBytes < minBytesDirect) |
| { |
| return @delegate.createOutput(name, context); |
| } |
| else |
| { |
| ensureCanWrite(name); |
| return new NativeUnixIndexOutput(new File(Directory, name), mergeBufferSize); |
| } |
| } |
| |
| private final static class NativeUnixIndexOutput extends IndexOutput |
| { |
| private final ByteBuffer buffer; |
| private final FileOutputStream fos; |
| private final FileChannel channel; |
| private final int bufferSize; |
| |
| //private final File path; |
| |
| private int bufferPos; |
| private long filePos; |
| private long fileLength; |
| private bool isOpen; |
| |
| public NativeUnixIndexOutput(File path, int bufferSize) throws IOException |
| { |
| //this.path = path; |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final java.io.FileDescriptor fd = NativePosixUtil.open_direct(path.toString(), false); |
| FileDescriptor fd = NativePosixUtil.open_direct(path.ToString(), false); |
| fos = new FileOutputStream(fd); |
| //fos = new FileOutputStream(path); |
| channel = fos.Channel; |
| buffer = ByteBuffer.allocateDirect(bufferSize); |
| this.bufferSize = bufferSize; |
| isOpen = true; |
| } |
| |
| public void writeByte(sbyte b) throws IOException |
| { |
| Debug.Assert(bufferPos == buffer.position(), "bufferPos=" + bufferPos + " vs buffer.position()=" + buffer.position()); |
| buffer.put(b); |
| if (++bufferPos == bufferSize) |
| { |
| dump(); |
| } |
| } |
| |
| public void writeBytes(sbyte[] src, int offset, int len) throws IOException |
| { |
| int toWrite = len; |
| while (true) |
| { |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final int left = bufferSize - bufferPos; |
| int left = bufferSize - bufferPos; |
| if (left <= toWrite) |
| { |
| buffer.put(src, offset, left); |
| toWrite -= left; |
| offset += left; |
| bufferPos = bufferSize; |
| dump(); |
| } |
| else |
| { |
| buffer.put(src, offset, toWrite); |
| bufferPos += toWrite; |
| break; |
| } |
| } |
| } |
| |
| //@Override |
| //public void setLength() throws IOException { |
| // TODO -- how to impl this? neither FOS nor |
| // FileChannel provides an API? |
| //} |
| |
| public void flush() |
| { |
| // TODO -- I don't think this method is necessary? |
| } |
| |
| private void dump() throws IOException |
| { |
| buffer.flip(); |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final long limit = filePos + buffer.limit(); |
| long limit = filePos + buffer.limit(); |
| if (limit > fileLength) |
| { |
| // this dump extends the file |
| fileLength = limit; |
| } |
| else |
| { |
| // we had seek'd back & wrote some changes |
| } |
| |
| // must always round to next block |
| buffer.limit((int)((buffer.limit() + ALIGN - 1) & ALIGN_NOT_MASK)); |
| |
| assert(buffer.limit() & ALIGN_NOT_MASK) == buffer.limit() : "limit=" + buffer.limit() + " vs " + (buffer.limit() & ALIGN_NOT_MASK); |
| assert(filePos & ALIGN_NOT_MASK) == filePos; |
| //System.out.println(Thread.currentThread().getName() + ": dump to " + filePos + " limit=" + buffer.limit() + " fos=" + fos); |
| channel.write(buffer, filePos); |
| filePos += bufferPos; |
| bufferPos = 0; |
| buffer.clear(); |
| //System.out.println("dump: done"); |
| |
| // TODO: the case where we'd seek'd back, wrote an |
| // entire buffer, we must here read the next buffer; |
| // likely Lucene won't trip on this since we only |
| // write smallish amounts on seeking back |
| } |
| |
| public long FilePointer |
| { |
| return filePos + bufferPos; |
| } |
| |
| // TODO: seek is fragile at best; it can only properly |
| // handle seek & then change bytes that fit entirely |
| // within one buffer |
| public void seek(long pos) throws IOException |
| { |
| if (pos != FilePointer) |
| { |
| dump(); |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final long alignedPos = pos & ALIGN_NOT_MASK; |
| long alignedPos = pos & ALIGN_NOT_MASK; |
| filePos = alignedPos; |
| int n = (int) NativePosixUtil.pread(fos.FD, filePos, buffer); |
| if (n < bufferSize) |
| { |
| buffer.limit(n); |
| } |
| //System.out.println("seek refill=" + n); |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final int delta = (int)(pos - alignedPos); |
| int delta = (int)(pos - alignedPos); |
| buffer.position(delta); |
| bufferPos = delta; |
| } |
| } |
| |
| public long length() |
| { |
| return fileLength + bufferPos; |
| } |
| |
| public long Checksum throws IOException |
| { |
| throw new System.NotSupportedException("this directory currently does not work at all!"); |
| } |
| |
| public void close() throws IOException |
| { |
| if (isOpen) |
| { |
| isOpen = false; |
| try |
| { |
| dump(); |
| } |
| finally |
| { |
| try |
| { |
| //System.out.println("direct close set len=" + fileLength + " vs " + channel.size() + " path=" + path); |
| channel.truncate(fileLength); |
| //System.out.println(" now: " + channel.size()); |
| } |
| finally |
| { |
| try |
| { |
| channel.close(); |
| } |
| finally |
| { |
| fos.close(); |
| //System.out.println(" final len=" + path.length()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private final static class NativeUnixIndexInput extends IndexInput |
| { |
| private final ByteBuffer buffer; |
| private final FileInputStream fis; |
| private final FileChannel channel; |
| private final int bufferSize; |
| |
| private bool isOpen; |
| private bool isClone; |
| private long filePos; |
| private int bufferPos; |
| |
| public NativeUnixIndexInput(File path, int bufferSize) throws IOException |
| { |
| base("NativeUnixIndexInput(path=\"" + path.Path + "\")"); |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final java.io.FileDescriptor fd = NativePosixUtil.open_direct(path.toString(), true); |
| FileDescriptor fd = NativePosixUtil.open_direct(path.ToString(), true); |
| fis = new FileInputStream(fd); |
| channel = fis.Channel; |
| this.bufferSize = bufferSize; |
| buffer = ByteBuffer.allocateDirect(bufferSize); |
| isOpen = true; |
| isClone = false; |
| filePos = -bufferSize; |
| bufferPos = bufferSize; |
| //System.out.println("D open " + path + " this=" + this); |
| } |
| |
| // for clone |
| public NativeUnixIndexInput(NativeUnixIndexInput other) throws IOException |
| { |
| base(other.ToString()); |
| this.fis = null; |
| channel = other.channel; |
| this.bufferSize = other.bufferSize; |
| buffer = ByteBuffer.allocateDirect(bufferSize); |
| filePos = -bufferSize; |
| bufferPos = bufferSize; |
| isOpen = true; |
| isClone = true; |
| //System.out.println("D clone this=" + this); |
| seek(other.FilePointer); |
| } |
| |
| public void close() throws IOException |
| { |
| if (isOpen && !isClone) |
| { |
| try |
| { |
| channel.close(); |
| } |
| finally |
| { |
| if (!isClone) |
| { |
| fis.close(); |
| } |
| } |
| } |
| } |
| |
| public long FilePointer |
| { |
| return filePos + bufferPos; |
| } |
| |
| public void seek(long pos) throws IOException |
| { |
| if (pos != FilePointer) |
| { |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final long alignedPos = pos & ALIGN_NOT_MASK; |
| long alignedPos = pos & ALIGN_NOT_MASK; |
| filePos = alignedPos - bufferSize; |
| |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final int delta = (int)(pos - alignedPos); |
| int delta = (int)(pos - alignedPos); |
| if (delta != 0) |
| { |
| refill(); |
| buffer.position(delta); |
| bufferPos = delta; |
| } |
| else |
| { |
| // force refill on next read |
| bufferPos = bufferSize; |
| } |
| } |
| } |
| |
| public long length() |
| { |
| try |
| { |
| return channel.size(); |
| } |
| catch (IOException ioe) |
| { |
| throw new Exception("IOException during length(): " + this, ioe); |
| } |
| } |
| |
| public sbyte readByte() throws IOException |
| { |
| // NOTE: we don't guard against EOF here... ie the |
| // "final" buffer will typically be filled to less |
| // than bufferSize |
| if (bufferPos == bufferSize) |
| { |
| refill(); |
| } |
| Debug.Assert(bufferPos == buffer.position(), "bufferPos=" + bufferPos + " vs buffer.position()=" + buffer.position()); |
| bufferPos++; |
| return buffer.get(); |
| } |
| |
| private void refill() throws IOException |
| { |
| buffer.clear(); |
| filePos += bufferSize; |
| bufferPos = 0; |
| assert(filePos & ALIGN_NOT_MASK) == filePos : "filePos=" + filePos + " anded=" + (filePos & ALIGN_NOT_MASK); |
| //System.out.println("X refill filePos=" + filePos); |
| int n; |
| try |
| { |
| n = channel.read(buffer, filePos); |
| } |
| catch (IOException ioe) |
| { |
| throw new IOException(ioe.Message + ": " + this, ioe); |
| } |
| if (n < 0) |
| { |
| throw new EOFException("read past EOF: " + this); |
| } |
| buffer.rewind(); |
| } |
| |
| public void readBytes(sbyte[] dst, int offset, int len) throws IOException |
| { |
| int toRead = len; |
| //System.out.println("\nX readBytes len=" + len + " fp=" + getFilePointer() + " size=" + length() + " this=" + this); |
| while (true) |
| { |
| //JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final': |
| //ORIGINAL LINE: final int left = bufferSize - bufferPos; |
| int left = bufferSize - bufferPos; |
| if (left < toRead) |
| { |
| //System.out.println(" copy " + left); |
| buffer.get(dst, offset, left); |
| toRead -= left; |
| offset += left; |
| refill(); |
| } |
| else |
| { |
| //System.out.println(" copy " + toRead); |
| buffer.get(dst, offset, toRead); |
| bufferPos += toRead; |
| //System.out.println(" readBytes done"); |
| break; |
| } |
| } |
| } |
| |
| public NativeUnixIndexInput MemberwiseClone() |
| { |
| try |
| { |
| return new NativeUnixIndexInput(this); |
| } |
| catch (IOException ioe) |
| { |
| throw new Exception("IOException during clone: " + this, ioe); |
| } |
| } |
| } |
| } |
| |
| } |