From 28f59a2e056fc5d65c6c34c87010cd177d843e81 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Wed, 24 Aug 2011 16:05:25 +0000 Subject: [PATCH] LUCENE-3218: Detach CompoundFileDirectory from Directory to prevent traps for delegating Directory implementations git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1161183 13f79535-47bb-0310-9956-ffa450edef68 --- .../lucene/store/NRTCachingDirectory.java | 27 ++- .../lucene/store/TestNRTCachingDirectory.java | 4 +- .../org/apache/lucene/index/IndexReader.java | 2 +- .../org/apache/lucene/index/IndexWriter.java | 4 +- .../lucene/index/SegmentCoreReaders.java | 8 +- .../org/apache/lucene/index/SegmentInfo.java | 9 +- .../apache/lucene/index/SegmentMerger.java | 2 +- .../codecs/DefaultDocValuesConsumer.java | 5 +- .../codecs/DefaultDocValuesProducer.java | 3 +- .../codecs/DefaultSegmentInfosReader.java | 9 +- .../lucene/store/CompoundFileDirectory.java | 187 +++++++++--------- .../lucene/store/CompoundFileWriter.java | 10 +- .../store/DefaultCompoundFileDirectory.java | 140 ------------- .../org/apache/lucene/store/Directory.java | 154 ++++++++++++--- .../lucene/store/FileSwitchDirectory.java | 16 +- .../apache/lucene/store/MMapDirectory.java | 53 ++--- .../apache/lucene/store/NIOFSDirectory.java | 57 ++---- .../lucene/store/SimpleFSDirectory.java | 64 +++--- .../MockCompoundFileDirectoryWrapper.java | 149 -------------- .../lucene/store/MockDirectoryWrapper.java | 71 +++++-- .../index/TestBackwardsCompatibility.java | 2 +- .../apache/lucene/index/TestCompoundFile.java | 56 +++--- .../lucene/index/TestIndexFileDeleter.java | 2 +- .../org/apache/lucene/index/index.34.cfs.zip | Bin 6103 -> 5203 bytes .../apache/lucene/index/index.34.nocfs.zip | Bin 12144 -> 12145 bytes .../lucene/store/TestFileSwitchDirectory.java | 4 +- 26 files changed, 419 insertions(+), 619 deletions(-) delete mode 100644 lucene/src/java/org/apache/lucene/store/DefaultCompoundFileDirectory.java delete mode 100644 lucene/src/test-framework/org/apache/lucene/store/MockCompoundFileDirectoryWrapper.java diff --git a/lucene/contrib/misc/src/java/org/apache/lucene/store/NRTCachingDirectory.java b/lucene/contrib/misc/src/java/org/apache/lucene/store/NRTCachingDirectory.java index 8ae8c9a07e0..c11b627e9d0 100644 --- a/lucene/contrib/misc/src/java/org/apache/lucene/store/NRTCachingDirectory.java +++ b/lucene/contrib/misc/src/java/org/apache/lucene/store/NRTCachingDirectory.java @@ -228,22 +228,21 @@ public class NRTCachingDirectory extends Directory { } } - // final due to LUCENE-3382: currently CFS backdoors the directory to create CFE - // by using the basic implementation and not delegating, we ensure that all - // openInput/createOutput requests come thru NRTCachingDirectory. - @Override - public final CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - return super.openCompoundInput(name, context); + public IndexInputSlicer createSlicer(final String name, final IOContext context) throws IOException { + ensureOpen(); + if (VERBOSE) { + System.out.println("nrtdir.openInput name=" + name); + } + if (cache.fileExists(name)) { + if (VERBOSE) { + System.out.println(" from cache"); + } + return cache.createSlicer(name, context); + } else { + return delegate.createSlicer(name, context); + } } - // final due to LUCENE-3382: currently CFS backdoors the directory to create CFE - // by using the basic implementation and not delegating, we ensure that all - // openInput/createOutput requests come thru NRTCachingDirectory. - @Override - public final CompoundFileDirectory createCompoundOutput(String name, IOContext context) throws IOException { - return super.createCompoundOutput(name, context); - } - /** Close this directory, which flushes any cached files * to the delegate and then closes the delegate. */ @Override diff --git a/lucene/contrib/misc/src/test/org/apache/lucene/store/TestNRTCachingDirectory.java b/lucene/contrib/misc/src/test/org/apache/lucene/store/TestNRTCachingDirectory.java index 833066afeb1..3efb3fdfe69 100644 --- a/lucene/contrib/misc/src/test/org/apache/lucene/store/TestNRTCachingDirectory.java +++ b/lucene/contrib/misc/src/test/org/apache/lucene/store/TestNRTCachingDirectory.java @@ -148,7 +148,7 @@ public class TestNRTCachingDirectory extends LuceneTestCase { // LUCENE-3382 test that delegate compound files correctly. public void testCompoundFileAppendTwice() throws IOException { Directory newDir = new NRTCachingDirectory(newDirectory(), 2.0, 25.0); - CompoundFileDirectory csw = newDir.createCompoundOutput("d.cfs", newIOContext(random)); + CompoundFileDirectory csw = new CompoundFileDirectory(newDir, "d.cfs", newIOContext(random), true); createSequenceFile(newDir, "d1", (byte) 0, 15); IndexOutput out = csw.createOutput("d.xyz", newIOContext(random)); out.writeInt(0); @@ -164,7 +164,7 @@ public class TestNRTCachingDirectory extends LuceneTestCase { csw.close(); - CompoundFileDirectory cfr = newDir.openCompoundInput("d.cfs", newIOContext(random)); + CompoundFileDirectory cfr = new CompoundFileDirectory(newDir, "d.cfs", newIOContext(random), false); assertEquals(1, cfr.listAll().length); assertEquals("d.xyz", cfr.listAll()[0]); cfr.close(); diff --git a/lucene/src/java/org/apache/lucene/index/IndexReader.java b/lucene/src/java/org/apache/lucene/index/IndexReader.java index 4ccab680c76..7dff757359d 100644 --- a/lucene/src/java/org/apache/lucene/index/IndexReader.java +++ b/lucene/src/java/org/apache/lucene/index/IndexReader.java @@ -1435,7 +1435,7 @@ public abstract class IndexReader implements Cloneable,Closeable { String dirname = file.getAbsoluteFile().getParent(); filename = file.getName(); dir = FSDirectory.open(new File(dirname)); - cfr = dir.openCompoundInput(filename, IOContext.DEFAULT); + cfr = new CompoundFileDirectory(dir, filename, IOContext.DEFAULT, false); String [] files = cfr.listAll(); ArrayUtil.mergeSort(files); // sort the array of filename so that the output is more readable diff --git a/lucene/src/java/org/apache/lucene/index/IndexWriter.java b/lucene/src/java/org/apache/lucene/index/IndexWriter.java index 2dd227a8940..c85157b8147 100644 --- a/lucene/src/java/org/apache/lucene/index/IndexWriter.java +++ b/lucene/src/java/org/apache/lucene/index/IndexWriter.java @@ -2262,7 +2262,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit { String compoundFileName = IndexFileNames.segmentFileName(newSegment.name, "", IndexFileNames.COMPOUND_FILE_EXTENSION); message("creating compound file " + compoundFileName); // Now build compound file - final Directory cfsDir = directory.createCompoundOutput(compoundFileName, context); + final Directory cfsDir = new CompoundFileDirectory(directory, compoundFileName, context, true); IOException prior = null; try { for(String fileName : newSegment.files()) { @@ -2594,7 +2594,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit { private void copySegmentIntoCFS(SegmentInfo info, String segName, IOContext context) throws IOException { String segFileName = IndexFileNames.segmentFileName(segName, "", IndexFileNames.COMPOUND_FILE_EXTENSION); Collection files = info.files(); - final CompoundFileDirectory cfsdir = directory.createCompoundOutput(segFileName, context); + final CompoundFileDirectory cfsdir = new CompoundFileDirectory(directory, segFileName, context, true); try { for (String file : files) { String newFileName = segName + IndexFileNames.stripSegmentName(file); diff --git a/lucene/src/java/org/apache/lucene/index/SegmentCoreReaders.java b/lucene/src/java/org/apache/lucene/index/SegmentCoreReaders.java index 2e392e535fc..c0ac39fe403 100644 --- a/lucene/src/java/org/apache/lucene/index/SegmentCoreReaders.java +++ b/lucene/src/java/org/apache/lucene/index/SegmentCoreReaders.java @@ -76,7 +76,7 @@ final class SegmentCoreReaders { try { Directory dir0 = dir; if (si.getUseCompoundFile()) { - cfsReader = dir.openCompoundInput(IndexFileNames.segmentFileName(segment, "", IndexFileNames.COMPOUND_FILE_EXTENSION), context); + cfsReader = new CompoundFileDirectory(dir, IndexFileNames.segmentFileName(segment, "", IndexFileNames.COMPOUND_FILE_EXTENSION), context, false); dir0 = cfsReader; } cfsDir = dir0; @@ -140,9 +140,9 @@ final class SegmentCoreReaders { if (si.getDocStoreOffset() != -1) { if (si.getDocStoreIsCompoundFile()) { assert storeCFSReader == null; - storeCFSReader = dir.openCompoundInput( + storeCFSReader = new CompoundFileDirectory(dir, IndexFileNames.segmentFileName(si.getDocStoreSegment(), "", IndexFileNames.COMPOUND_FILE_STORE_EXTENSION), - context); + context, false); storeDir = storeCFSReader; assert storeDir != null; } else { @@ -154,7 +154,7 @@ final class SegmentCoreReaders { // was not used, but then we are asked to open doc // stores after the segment has switched to CFS if (cfsReader == null) { - cfsReader = dir.openCompoundInput(IndexFileNames.segmentFileName(segment, "", IndexFileNames.COMPOUND_FILE_EXTENSION), context); + cfsReader = new CompoundFileDirectory(dir,IndexFileNames.segmentFileName(segment, "", IndexFileNames.COMPOUND_FILE_EXTENSION), context, false); } storeDir = cfsReader; assert storeDir != null; diff --git a/lucene/src/java/org/apache/lucene/index/SegmentInfo.java b/lucene/src/java/org/apache/lucene/index/SegmentInfo.java index 8580f2721c4..ad87a91c86b 100644 --- a/lucene/src/java/org/apache/lucene/index/SegmentInfo.java +++ b/lucene/src/java/org/apache/lucene/index/SegmentInfo.java @@ -30,6 +30,7 @@ import java.util.Set; import org.apache.lucene.index.codecs.Codec; import org.apache.lucene.index.codecs.CodecProvider; import org.apache.lucene.index.codecs.DefaultSegmentInfosWriter; +import org.apache.lucene.store.CompoundFileDirectory; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexInput; @@ -247,7 +248,7 @@ public final class SegmentInfo implements Cloneable { } final Directory dirToTest; if (isCompoundFile) { - dirToTest = dir.openCompoundInput(IndexFileNames.segmentFileName(storesSegment, "", ext), IOContext.READONCE); + dirToTest = new CompoundFileDirectory(dir, IndexFileNames.segmentFileName(storesSegment, "", ext), IOContext.READONCE, false); } else { dirToTest = dir; } @@ -265,8 +266,8 @@ public final class SegmentInfo implements Cloneable { if (fieldInfos == null) { Directory dir0 = dir; if (isCompoundFile && checkCompoundFile) { - dir0 = dir.openCompoundInput(IndexFileNames.segmentFileName(name, - "", IndexFileNames.COMPOUND_FILE_EXTENSION), IOContext.READONCE); + dir0 = new CompoundFileDirectory(dir, IndexFileNames.segmentFileName(name, + "", IndexFileNames.COMPOUND_FILE_EXTENSION), IOContext.READONCE, false); } try { fieldInfos = new FieldInfos(dir0, IndexFileNames.segmentFileName(name, @@ -619,7 +620,7 @@ public final class SegmentInfo implements Cloneable { if (useCompoundFile) { fileSet.add(IndexFileNames.segmentFileName(name, "", IndexFileNames.COMPOUND_FILE_EXTENSION)); - if (version != null && StringHelper.getVersionComparator().compare("3.4", version) <= 0) { + if (version != null && StringHelper.getVersionComparator().compare("4.0", version) <= 0) { fileSet.add(IndexFileNames.segmentFileName(name, "", IndexFileNames.COMPOUND_FILE_ENTRIES_EXTENSION)); } diff --git a/lucene/src/java/org/apache/lucene/index/SegmentMerger.java b/lucene/src/java/org/apache/lucene/index/SegmentMerger.java index 96908747a35..0abb9e7958d 100644 --- a/lucene/src/java/org/apache/lucene/index/SegmentMerger.java +++ b/lucene/src/java/org/apache/lucene/index/SegmentMerger.java @@ -153,7 +153,7 @@ final class SegmentMerger { // Now merge all added files Collection files = info.files(); - CompoundFileDirectory cfsDir = directory.createCompoundOutput(fileName, context); + CompoundFileDirectory cfsDir = new CompoundFileDirectory(directory, fileName, context, true); try { for (String file : files) { assert !IndexFileNames.matchesExtension(file, IndexFileNames.DELETES_EXTENSION) diff --git a/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesConsumer.java b/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesConsumer.java index d1749fb320a..ee892c9a210 100644 --- a/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesConsumer.java +++ b/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesConsumer.java @@ -28,6 +28,7 @@ import org.apache.lucene.index.IndexFileNames; import org.apache.lucene.index.PerDocWriteState; import org.apache.lucene.index.SegmentInfo; import org.apache.lucene.index.values.Writer; +import org.apache.lucene.store.CompoundFileDirectory; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.util.BytesRef; @@ -51,9 +52,9 @@ public class DefaultDocValuesConsumer extends PerDocConsumer { this.bytesUsed = state.bytesUsed; this.context = state.context; //TODO maybe we should enable a global CFS that all codecs can pull on demand to further reduce the number of files? - this.directory = useCompoundFile ? state.directory.createCompoundOutput( + this.directory = useCompoundFile ? new CompoundFileDirectory(state.directory, IndexFileNames.segmentFileName(segmentName, codecId, - IndexFileNames.COMPOUND_FILE_EXTENSION), context) : state.directory; + IndexFileNames.COMPOUND_FILE_EXTENSION), context, true) : state.directory; this.comparator = comparator; this.useCompoundFile = useCompoundFile; } diff --git a/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesProducer.java b/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesProducer.java index 6a3207d2b96..ad4920fbbdd 100644 --- a/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesProducer.java +++ b/lucene/src/java/org/apache/lucene/index/codecs/DefaultDocValuesProducer.java @@ -32,6 +32,7 @@ import org.apache.lucene.index.values.IndexDocValues; import org.apache.lucene.index.values.Floats; import org.apache.lucene.index.values.Ints; import org.apache.lucene.index.values.ValueType; +import org.apache.lucene.store.CompoundFileDirectory; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.util.BytesRef; @@ -78,7 +79,7 @@ public class DefaultDocValuesProducer extends PerDocValues { this.sortComparator = sortComparator; final Directory directory; if (useCompoundFile) { - cfs = directory = dir.openCompoundInput(IndexFileNames.segmentFileName(si.name, codecId, IndexFileNames.COMPOUND_FILE_EXTENSION), context); + cfs = directory = new CompoundFileDirectory(dir, IndexFileNames.segmentFileName(si.name, codecId, IndexFileNames.COMPOUND_FILE_EXTENSION), context, false); } else { cfs = null; directory = dir; diff --git a/lucene/src/java/org/apache/lucene/index/codecs/DefaultSegmentInfosReader.java b/lucene/src/java/org/apache/lucene/index/codecs/DefaultSegmentInfosReader.java index 443d41d3f93..7ae303490f7 100644 --- a/lucene/src/java/org/apache/lucene/index/codecs/DefaultSegmentInfosReader.java +++ b/lucene/src/java/org/apache/lucene/index/codecs/DefaultSegmentInfosReader.java @@ -27,6 +27,7 @@ import org.apache.lucene.index.IndexFormatTooNewException; import org.apache.lucene.index.SegmentInfo; import org.apache.lucene.index.SegmentInfos; import org.apache.lucene.store.ChecksumIndexInput; +import org.apache.lucene.store.CompoundFileDirectory; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexInput; @@ -68,13 +69,13 @@ public class DefaultSegmentInfosReader extends SegmentInfosReader { Directory dir = directory; if (si.getDocStoreOffset() != -1) { if (si.getDocStoreIsCompoundFile()) { - dir = dir.openCompoundInput(IndexFileNames.segmentFileName( + dir = new CompoundFileDirectory(dir, IndexFileNames.segmentFileName( si.getDocStoreSegment(), "", - IndexFileNames.COMPOUND_FILE_STORE_EXTENSION), context); + IndexFileNames.COMPOUND_FILE_STORE_EXTENSION), context, false); } } else if (si.getUseCompoundFile()) { - dir = dir.openCompoundInput(IndexFileNames.segmentFileName( - si.name, "", IndexFileNames.COMPOUND_FILE_EXTENSION), context); + dir = new CompoundFileDirectory(dir,IndexFileNames.segmentFileName( + si.name, "", IndexFileNames.COMPOUND_FILE_EXTENSION), context, false); } try { diff --git a/lucene/src/java/org/apache/lucene/store/CompoundFileDirectory.java b/lucene/src/java/org/apache/lucene/store/CompoundFileDirectory.java index 1f9eb222b03..09acf9106ab 100644 --- a/lucene/src/java/org/apache/lucene/store/CompoundFileDirectory.java +++ b/lucene/src/java/org/apache/lucene/store/CompoundFileDirectory.java @@ -34,7 +34,7 @@ import java.io.IOException; * Directory methods that would normally modify data throw an exception. * @lucene.experimental */ -public abstract class CompoundFileDirectory extends Directory { +public final class CompoundFileDirectory extends Directory { /** Offset/Length for a slice inside of a compound file */ public static final class FileEntry { @@ -45,68 +45,86 @@ public abstract class CompoundFileDirectory extends Directory { private final Directory directory; private final String fileName; protected final int readBufferSize; - private Map entries; - private boolean openForWrite; + private final Map entries; + private final boolean openForWrite; private static final Map SENTINEL = Collections.emptyMap(); - private CompoundFileWriter writer; + private final CompoundFileWriter writer; + private final IndexInputSlicer handle; /** * Create a new CompoundFileDirectory. *

* NOTE: subclasses must call {@link #initForRead(Map)} before the directory can be used. */ - public CompoundFileDirectory(Directory directory, String fileName, IOContext context) throws IOException { - + public CompoundFileDirectory(Directory directory, String fileName, IOContext context, boolean openForWrite) throws IOException { this.directory = directory; this.fileName = fileName; this.readBufferSize = BufferedIndexInput.bufferSize(context); this.isOpen = false; + this.openForWrite = openForWrite; + if (!openForWrite) { + boolean success = false; + handle = directory.createSlicer(fileName, context); + try { + this.entries = readEntries(handle, directory, fileName); + success = true; + } finally { + if (!success) { + IOUtils.closeSafely(true, handle); + } + } + this.isOpen = true; + writer = null; + } else { + assert !(directory instanceof CompoundFileDirectory) : "compound file inside of compound file: " + fileName; + this.entries = SENTINEL; + this.isOpen = true; + writer = new CompoundFileWriter(directory, fileName); + handle = null; + } } - - /** Initialize with a map of filename->slices */ - protected final void initForRead(Map entries) { - this.entries = entries; - this.isOpen = true; - this.openForWrite = false; - } - - protected final void initForWrite() throws IOException { - assert !(directory instanceof CompoundFileDirectory) : "compound file inside of compound file: " + fileName; - this.entries = SENTINEL; - this.openForWrite = true; - this.isOpen = true; - writer = new CompoundFileWriter(directory, fileName); - } - + /** Helper method that reads CFS entries from an input stream */ - public static final Map readEntries(IndexInput stream, Directory dir, String name) throws IOException { + private static final Map readEntries( + IndexInputSlicer handle, Directory dir, String name) throws IOException { // read the first VInt. If it is negative, it's the version number // otherwise it's the count (pre-3.1 indexes) - final int firstInt = stream.readVInt(); - if (firstInt == CompoundFileWriter.FORMAT_CURRENT) { - IndexInput input = null; - try { - input = dir.openInput(IndexFileNames.segmentFileName(IndexFileNames.stripExtension(name), "", - IndexFileNames.COMPOUND_FILE_ENTRIES_EXTENSION), IOContext.READONCE); - final int readInt = input.readInt(); // unused right now - assert readInt == CompoundFileWriter.ENTRY_FORMAT_CURRENT; - final int numEntries = input.readVInt(); - final Map mapping = new HashMap( - numEntries); - for (int i = 0; i < numEntries; i++) { - final FileEntry fileEntry = new FileEntry(); - mapping.put(input.readString(), fileEntry); - fileEntry.offset = input.readLong(); - fileEntry.length = input.readLong(); + final IndexInput stream = handle.openFullSlice(); + final Map mapping; + boolean success = false; + try { + final int firstInt = stream.readVInt(); + if (firstInt == CompoundFileWriter.FORMAT_CURRENT) { + IndexInput input = null; + try { + input = dir.openInput(IndexFileNames.segmentFileName( + IndexFileNames.stripExtension(name), "", + IndexFileNames.COMPOUND_FILE_ENTRIES_EXTENSION), + IOContext.READONCE); + final int readInt = input.readInt(); // unused right now + assert readInt == CompoundFileWriter.ENTRY_FORMAT_CURRENT; + final int numEntries = input.readVInt(); + mapping = new HashMap( + numEntries); + for (int i = 0; i < numEntries; i++) { + final FileEntry fileEntry = new FileEntry(); + mapping.put(input.readString(), fileEntry); + fileEntry.offset = input.readLong(); + fileEntry.length = input.readLong(); + } + return mapping; + } finally { + IOUtils.closeSafely(true, input); } - return mapping; - } finally { - IOUtils.closeSafely(true, input); + } else { + // TODO remove once 3.x is not supported anymore + mapping = readLegacyEntries(stream, firstInt); } + success = true; + return mapping; + } finally { + IOUtils.closeSafely(!success, stream); } - - // TODO remove once 3.x is not supported anymore - return readLegacyEntries(stream, firstInt); } private static Map readLegacyEntries(IndexInput stream, @@ -173,32 +191,29 @@ public abstract class CompoundFileDirectory extends Directory { public synchronized void close() throws IOException { if (!isOpen) { // allow double close - usually to be consistent with other closeables - assert entries == null; return; // already closed } - entries = null; isOpen = false; if (writer != null) { assert openForWrite; writer.close(); + } else { + IOUtils.closeSafely(false, handle); } } @Override - public synchronized IndexInput openInput(String fileName, IOContext context) throws IOException { + public synchronized IndexInput openInput(String name, IOContext context) throws IOException { ensureOpen(); assert !openForWrite; - final String id = IndexFileNames.stripSegmentName(fileName); + final String id = IndexFileNames.stripSegmentName(name); final FileEntry entry = entries.get(id); if (entry == null) { - throw new IOException("No sub-file with id " + id + " found (fileName=" + fileName + " files: " + entries.keySet() + ")"); + throw new IOException("No sub-file with id " + id + " found (fileName=" + name + " files: " + entries.keySet() + ")"); } - return openInputSlice(id, entry.offset, entry.length, readBufferSize); + return handle.openSlice(entry.offset, entry.length); } - /** Return an IndexInput that represents a "slice" or portion of the CFS file. */ - public abstract IndexInput openInputSlice(String id, long offset, long length, int readBufferSize) throws IOException; - /** Returns an array of strings, one for each file in the directory. */ @Override public String[] listAll() { @@ -279,51 +294,31 @@ public abstract class CompoundFileDirectory extends Directory { public Lock makeLock(String name) { throw new UnsupportedOperationException(); } - + @Override - public CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - FileEntry fileEntry = this.entries.get(IndexFileNames.stripSegmentName(name)); - if (fileEntry == null) { - throw new FileNotFoundException("file " + name + " does not exists in this CFS"); - } - return new NestedCompoundFileDirectory(name, context, fileEntry.offset, fileEntry.length); - } - - /** Not implemented - * @throws UnsupportedOperationException */ - @Override - public CompoundFileDirectory createCompoundOutput(String name, IOContext context) + public IndexInputSlicer createSlicer(final String name, IOContext context) throws IOException { - throw new UnsupportedOperationException("can not create nested CFS, create seperately and use Directory.copy instead"); - } - - private class NestedCompoundFileDirectory extends CompoundFileDirectory { - - private final long cfsOffset; - private final long cfsLength; - - public NestedCompoundFileDirectory(String fileName, IOContext context, long offset, long length) - throws IOException { - super(directory, fileName, context); - this.cfsOffset = offset; - this.cfsLength = length; - IndexInput input = null; - try { - input = CompoundFileDirectory.this.openInput(fileName, IOContext.READONCE); - initForRead(CompoundFileDirectory.readEntries(input, - CompoundFileDirectory.this, fileName)); - } finally { - IOUtils.closeSafely(false, input); + ensureOpen(); + assert !openForWrite; + final String id = IndexFileNames.stripSegmentName(name); + final FileEntry entry = entries.get(id); + if (entry == null) { + throw new IOException("No sub-file with id " + id + " found (fileName=" + name + " files: " + entries.keySet() + ")"); + } + return new IndexInputSlicer() { + @Override + public void close() throws IOException { + } + + @Override + public IndexInput openSlice(long offset, long length) throws IOException { + return handle.openSlice(entry.offset + offset, length); } - } - @Override - public IndexInput openInputSlice(String id, long offset, long length, - int readBufferSize) throws IOException { - assert offset + length <= cfsLength; - return CompoundFileDirectory.this.openInputSlice(id, cfsOffset + offset, length, readBufferSize); - } - + @Override + public IndexInput openFullSlice() throws IOException { + return openSlice(0, entry.length); + } + }; } - } diff --git a/lucene/src/java/org/apache/lucene/store/CompoundFileWriter.java b/lucene/src/java/org/apache/lucene/store/CompoundFileWriter.java index eeeaf2f4a76..80a1367ee48 100644 --- a/lucene/src/java/org/apache/lucene/store/CompoundFileWriter.java +++ b/lucene/src/java/org/apache/lucene/store/CompoundFileWriter.java @@ -190,6 +190,7 @@ final class CompoundFileWriter implements Closeable{ private final long copyFileEntry(IndexOutput dataOut, FileEntry fileEntry) throws IOException, MergeAbortedException { final IndexInput is = fileEntry.dir.openInput(fileEntry.file, IOContext.READONCE); + boolean success = false; try { final long startPtr = dataOut.getFilePointer(); final long length = fileEntry.length; @@ -201,11 +202,14 @@ final class CompoundFileWriter implements Closeable{ throw new IOException("Difference in the output file offsets " + diff + " does not match the original file length " + length); fileEntry.offset = startPtr; - // copy successful - delete file - fileEntry.dir.deleteFile(fileEntry.file); + success = true; return length; } finally { - is.close(); + IOUtils.closeSafely(!success, is); + if (success) { + // copy successful - delete file + fileEntry.dir.deleteFile(fileEntry.file); + } } } diff --git a/lucene/src/java/org/apache/lucene/store/DefaultCompoundFileDirectory.java b/lucene/src/java/org/apache/lucene/store/DefaultCompoundFileDirectory.java deleted file mode 100644 index 35c036b664a..00000000000 --- a/lucene/src/java/org/apache/lucene/store/DefaultCompoundFileDirectory.java +++ /dev/null @@ -1,140 +0,0 @@ -package 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. - */ - -import java.io.IOException; - -import org.apache.lucene.util.IOUtils; - -/** - * Default implementation of {@link CompoundFileDirectory}. - *

- * This implementation returns a BufferedIndexInput that wraps the underlying - * Directory's IndexInput for the compound file (using unbuffered reads). - * @lucene.experimental - */ -public class DefaultCompoundFileDirectory extends CompoundFileDirectory { - protected IndexInput stream; - - public DefaultCompoundFileDirectory(Directory directory, String fileName, IOContext context, boolean writeable) throws IOException { - super(directory, fileName, context); - if (!writeable) { - try { - stream = directory.openInput(fileName, context); - initForRead(CompoundFileDirectory.readEntries(stream, directory, fileName)); - } catch (IOException e) { - IOUtils.closeSafely(e, stream); - } - } else { - initForWrite(); - } - } - - @Override - public IndexInput openInputSlice(String id, long offset, long length, int readBufferSize) throws IOException { - return new CSIndexInput(stream, offset, length, readBufferSize); - } - - @Override - public synchronized void close() throws IOException { - try { - IOUtils.closeSafely(false, stream); - } finally { - super.close(); - } - } - - /** Implementation of an IndexInput that reads from a portion of the - * compound file. - */ - static final class CSIndexInput extends BufferedIndexInput { - IndexInput base; - long fileOffset; - long length; - - CSIndexInput(final IndexInput base, final long fileOffset, final long length) { - this(base, fileOffset, length, BufferedIndexInput.BUFFER_SIZE); - } - - CSIndexInput(final IndexInput base, final long fileOffset, final long length, int readBufferSize) { - super(readBufferSize); - this.base = (IndexInput)base.clone(); - this.fileOffset = fileOffset; - this.length = length; - } - - @Override - public Object clone() { - CSIndexInput clone = (CSIndexInput)super.clone(); - clone.base = (IndexInput)base.clone(); - clone.fileOffset = fileOffset; - clone.length = length; - return clone; - } - - /** Expert: implements buffer refill. Reads bytes from the current - * position in the input. - * @param b the array to read bytes into - * @param offset the offset in the array to start storing bytes - * @param len the number of bytes to read - */ - @Override - protected void readInternal(byte[] b, int offset, int len) throws IOException { - long start = getFilePointer(); - if(start + len > length) - throw new IOException("read past EOF"); - base.seek(fileOffset + start); - base.readBytes(b, offset, len, false); - } - - /** Expert: implements seek. Sets current position in this file, where - * the next {@link #readInternal(byte[],int,int)} will occur. - * @see #readInternal(byte[],int,int) - */ - @Override - protected void seekInternal(long pos) {} - - /** Closes the stream to further operations. */ - @Override - public void close() throws IOException { - base.close(); - } - - @Override - public long length() { - return length; - } - - @Override - public void copyBytes(IndexOutput out, long numBytes) throws IOException { - // Copy first whatever is in the buffer - numBytes -= flushBuffer(out, numBytes); - - // If there are more bytes left to copy, delegate the copy task to the - // base IndexInput, in case it can do an optimized copy. - if (numBytes > 0) { - long start = getFilePointer(); - if (start + numBytes > length) { - throw new IOException("read past EOF"); - } - base.seek(fileOffset + start); - base.copyBytes(out, numBytes); - } - } - } -} diff --git a/lucene/src/java/org/apache/lucene/store/Directory.java b/lucene/src/java/org/apache/lucene/store/Directory.java index dff4b08f32b..58fd3ca87bc 100644 --- a/lucene/src/java/org/apache/lucene/store/Directory.java +++ b/lucene/src/java/org/apache/lucene/store/Directory.java @@ -112,30 +112,6 @@ public abstract class Directory implements Closeable { */ public abstract IndexInput openInput(String name, IOContext context) throws IOException; - /** - * Returns a {@link CompoundFileDirectory} capable of - * reading the Lucene compound file format. - *

- * The default implementation returns - * {@link DefaultCompoundFileDirectory}. - * @lucene.experimental - */ - public CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - return new DefaultCompoundFileDirectory(this, name, context, false); - } - - /** - * Returns a {@link CompoundFileDirectory} capable of - * writing the Lucene compound file format. - *

- * The default implementation returns - * {@link DefaultCompoundFileDirectory}. - * @lucene.experimental - */ - public CompoundFileDirectory createCompoundOutput(String name, IOContext context) throws IOException { - return new DefaultCompoundFileDirectory(this, name, context, true); - } - /** Construct a {@link Lock}. * @param name the name of the lock file */ @@ -232,6 +208,37 @@ public abstract class Directory implements Closeable { } } + /** + * Creates an {@link IndexInputSlicer} for the given file name. + * IndexInputSlicer allows other {@link Directory} implementations to + * efficiently open one or more sliced {@link IndexInput} instances from a + * single file handle. The underlying file handle is kept open until the + * {@link IndexInputSlicer} is closed. + * + * @throws IOException + * if an {@link IOException} occurs + * @lucene.internal + * @lucene.experimental + */ + public IndexInputSlicer createSlicer(final String name, final IOContext context) throws IOException { + ensureOpen(); + return new IndexInputSlicer() { + private final IndexInput base = Directory.this.openInput(name, context); + @Override + public IndexInput openSlice(long offset, long length) { + return new SlicedIndexInput(base, offset, length); + } + @Override + public void close() throws IOException { + base.close(); + } + @Override + public IndexInput openFullSlice() throws IOException { + return (IndexInput) base.clone(); + } + }; + } + /** * @throws AlreadyClosedException if this Directory is closed */ @@ -239,4 +246,103 @@ public abstract class Directory implements Closeable { if (!isOpen) throw new AlreadyClosedException("this Directory is closed"); } + + /** + * Allows to create one or more sliced {@link IndexInput} instances from a single + * file handle. Some {@link Directory} implementations may be able to efficiently map slices of a file + * into memory when only certain parts of a file are required. + * @lucene.internal + * @lucene.experimental + */ + public abstract class IndexInputSlicer implements Closeable { + /** + * Returns an {@link IndexInput} slice starting at the given offset with the given length. + */ + public abstract IndexInput openSlice(long offset, long length) throws IOException; + + /** + * Returns an {@link IndexInput} slice starting at offset 0 with a + * length equal to the length of the underlying file + */ + public abstract IndexInput openFullSlice() throws IOException; + } + + /** Implementation of an IndexInput that reads from a portion of + * a file. + */ + private static final class SlicedIndexInput extends BufferedIndexInput { + IndexInput base; + long fileOffset; + long length; + + SlicedIndexInput(final IndexInput base, final long fileOffset, final long length) { + this(base, fileOffset, length, BufferedIndexInput.BUFFER_SIZE); + } + + SlicedIndexInput(final IndexInput base, final long fileOffset, final long length, int readBufferSize) { + super(readBufferSize); + this.base = (IndexInput) base.clone(); + this.fileOffset = fileOffset; + this.length = length; + } + + @Override + public Object clone() { + SlicedIndexInput clone = (SlicedIndexInput)super.clone(); + clone.base = (IndexInput)base.clone(); + clone.fileOffset = fileOffset; + clone.length = length; + return clone; + } + + /** Expert: implements buffer refill. Reads bytes from the current + * position in the input. + * @param b the array to read bytes into + * @param offset the offset in the array to start storing bytes + * @param len the number of bytes to read + */ + @Override + protected void readInternal(byte[] b, int offset, int len) throws IOException { + long start = getFilePointer(); + if(start + len > length) + throw new IOException("read past EOF"); + base.seek(fileOffset + start); + base.readBytes(b, offset, len, false); + } + + /** Expert: implements seek. Sets current position in this file, where + * the next {@link #readInternal(byte[],int,int)} will occur. + * @see #readInternal(byte[],int,int) + */ + @Override + protected void seekInternal(long pos) {} + + /** Closes the stream to further operations. */ + @Override + public void close() throws IOException { + base.close(); + } + + @Override + public long length() { + return length; + } + + @Override + public void copyBytes(IndexOutput out, long numBytes) throws IOException { + // Copy first whatever is in the buffer + numBytes -= flushBuffer(out, numBytes); + + // If there are more bytes left to copy, delegate the copy task to the + // base IndexInput, in case it can do an optimized copy. + if (numBytes > 0) { + long start = getFilePointer(); + if (start + numBytes > length) { + throw new IOException("read past EOF"); + } + base.seek(fileOffset + start); + base.copyBytes(out, numBytes); + } + } + } } diff --git a/lucene/src/java/org/apache/lucene/store/FileSwitchDirectory.java b/lucene/src/java/org/apache/lucene/store/FileSwitchDirectory.java index f94af04f361..2abf48c28af 100644 --- a/lucene/src/java/org/apache/lucene/store/FileSwitchDirectory.java +++ b/lucene/src/java/org/apache/lucene/store/FileSwitchDirectory.java @@ -177,19 +177,9 @@ public class FileSwitchDirectory extends Directory { return getDirectory(name).openInput(name, context); } - // final due to LUCENE-3380: currently CFS backdoors the directory to create CFE - // by using the basic implementation and not delegating, we ensure that all - // openInput/createOutput requests come thru NRTCachingDirectory. @Override - public final CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - return super.openCompoundInput(name, context); - } - - // final due to LUCENE-3380: currently CFS backdoors the directory to create CFE - // by using the basic implementation and not delegating, we ensure that all - // openInput/createOutput requests come thru NRTCachingDirectory. - @Override - public final CompoundFileDirectory createCompoundOutput(String name, IOContext context) throws IOException { - return super.createCompoundOutput(name, context); + public IndexInputSlicer createSlicer(String name, IOContext context) + throws IOException { + return getDirectory(name).createSlicer(name, context); } } diff --git a/lucene/src/java/org/apache/lucene/store/MMapDirectory.java b/lucene/src/java/org/apache/lucene/store/MMapDirectory.java index 1b8cb5ad0eb..a5f406b8910 100644 --- a/lucene/src/java/org/apache/lucene/store/MMapDirectory.java +++ b/lucene/src/java/org/apache/lucene/store/MMapDirectory.java @@ -32,7 +32,6 @@ import java.security.PrivilegedActionException; import java.lang.reflect.Method; import org.apache.lucene.util.Constants; -import org.apache.lucene.util.IOUtils; /** File-based {@link Directory} implementation that uses * mmap for reading, and {@link @@ -220,42 +219,26 @@ public class MMapDirectory extends FSDirectory { } } - @Override - public CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - return new MMapCompoundFileDirectory(name, context); - } - - private final class MMapCompoundFileDirectory extends CompoundFileDirectory { - private RandomAccessFile raf = null; - - public MMapCompoundFileDirectory(String fileName, IOContext context) throws IOException { - super(MMapDirectory.this, fileName, context); - IndexInput stream = null; - try { - File f = new File(MMapDirectory.this.getDirectory(), fileName); - raf = new RandomAccessFile(f, "r"); - stream = new MMapIndexInput(raf, 0, raf.length(), chunkSizePower); - initForRead(CompoundFileDirectory.readEntries(stream, MMapDirectory.this, fileName)); - stream.close(); - } catch (IOException e) { - // throw our original exception - IOUtils.closeSafely(e, raf, stream); - } - } - - @Override - public IndexInput openInputSlice(String id, long offset, long length, int readBufferSize) throws IOException { - return new MMapIndexInput(raf, offset, length, chunkSizePower); - } - - @Override - public synchronized void close() throws IOException { - try { + public IndexInputSlicer createSlicer(final String name, final IOContext context) throws IOException { + ensureOpen(); + File f = new File(getDirectory(), name); + final RandomAccessFile raf = new RandomAccessFile(f, "r"); + return new IndexInputSlicer() { + @Override + public void close() throws IOException { raf.close(); - } finally { - super.close(); } - } + + @Override + public IndexInput openSlice(long offset, long length) throws IOException { + return new MMapIndexInput(raf, offset, length, chunkSizePower); + } + + @Override + public IndexInput openFullSlice() throws IOException { + return openSlice(0, raf.length()); + } + }; } // Because Java's ByteBuffer uses an int to address the diff --git a/lucene/src/java/org/apache/lucene/store/NIOFSDirectory.java b/lucene/src/java/org/apache/lucene/store/NIOFSDirectory.java index 6ac4380b55f..42c18216309 100644 --- a/lucene/src/java/org/apache/lucene/store/NIOFSDirectory.java +++ b/lucene/src/java/org/apache/lucene/store/NIOFSDirectory.java @@ -24,8 +24,7 @@ import java.nio.channels.ClosedChannelException; // javadoc @link import java.nio.channels.FileChannel; import java.util.concurrent.Future; // javadoc -import org.apache.lucene.store.SimpleFSDirectory.SimpleFSIndexInput; -import org.apache.lucene.util.IOUtils; +import org.apache.lucene.store.SimpleFSDirectory.SimpleFSIndexInput.Descriptor; /** * An {@link FSDirectory} implementation that uses java.nio's FileChannel's @@ -81,45 +80,29 @@ public class NIOFSDirectory extends FSDirectory { return new NIOFSIndexInput(new File(getDirectory(), name), context, getReadChunkSize()); } - @Override - public CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - return new NIOFSCompoundFileDirectory(name, context); - } + public IndexInputSlicer createSlicer(final String name, + final IOContext context) throws IOException { + ensureOpen(); + final File file = new File(getDirectory(), name); + final Descriptor descriptor = new Descriptor(file, "r"); + return new Directory.IndexInputSlicer() { - private final class NIOFSCompoundFileDirectory extends CompoundFileDirectory { - private SimpleFSIndexInput.Descriptor fd; - private FileChannel fc; - - public NIOFSCompoundFileDirectory(String fileName, IOContext context) throws IOException { - super(NIOFSDirectory.this, fileName, context); - IndexInput stream = null; - try { - File f = new File(NIOFSDirectory.this.getDirectory(), fileName); - fd = new SimpleFSIndexInput.Descriptor(f, "r"); - fc = fd.getChannel(); - stream = new NIOFSIndexInput(fd, fc, 0, fd.length, readBufferSize, - getReadChunkSize()); - initForRead(CompoundFileDirectory.readEntries(stream, NIOFSDirectory.this, fileName)); - stream.close(); - } catch (IOException e) { - // throw our original exception - IOUtils.closeSafely(e, fc, fd, stream); + @Override + public void close() throws IOException { + descriptor.close(); } - } - - @Override - public IndexInput openInputSlice(String id, long offset, long length, int readBufferSize) throws IOException { - return new NIOFSIndexInput(fd, fc, offset, length, readBufferSize, getReadChunkSize()); - } - @Override - public synchronized void close() throws IOException { - try { - IOUtils.closeSafely(false, fc, fd); - } finally { - super.close(); + @Override + public IndexInput openSlice(long offset, long length) throws IOException { + return new NIOFSIndexInput(descriptor, descriptor.getChannel(), offset, + length, BufferedIndexInput.bufferSize(context), getReadChunkSize()); } - } + + @Override + public IndexInput openFullSlice() throws IOException { + return openSlice(0, descriptor.length); + } + }; } protected static class NIOFSIndexInput extends SimpleFSDirectory.SimpleFSIndexInput { diff --git a/lucene/src/java/org/apache/lucene/store/SimpleFSDirectory.java b/lucene/src/java/org/apache/lucene/store/SimpleFSDirectory.java index 8174fa4b2b7..ba7c220183b 100644 --- a/lucene/src/java/org/apache/lucene/store/SimpleFSDirectory.java +++ b/lucene/src/java/org/apache/lucene/store/SimpleFSDirectory.java @@ -21,8 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; -import org.apache.lucene.util.IOUtils; - +import org.apache.lucene.store.SimpleFSDirectory.SimpleFSIndexInput.Descriptor; /** A straightforward implementation of {@link FSDirectory} * using java.io.RandomAccessFile. However, this class has @@ -59,44 +58,33 @@ public class SimpleFSDirectory extends FSDirectory { return new SimpleFSIndexInput(new File(directory, name), context, getReadChunkSize()); } - @Override - public CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - return new SimpleFSCompoundFileDirectory(name, context); + + + public IndexInputSlicer createSlicer(final String name, + final IOContext context) throws IOException { + ensureOpen(); + final File file = new File(getDirectory(), name); + final Descriptor descriptor = new Descriptor(file, "r"); + return new IndexInputSlicer() { + + @Override + public void close() throws IOException { + descriptor.close(); + } + + @Override + public IndexInput openSlice(long offset, long length) throws IOException { + return new SimpleFSIndexInput(descriptor, offset, + length, BufferedIndexInput.bufferSize(context), getReadChunkSize()); + } + + @Override + public IndexInput openFullSlice() throws IOException { + return openSlice(0, descriptor.length); + } + }; } - private final class SimpleFSCompoundFileDirectory extends CompoundFileDirectory { - private SimpleFSIndexInput.Descriptor fd; - - public SimpleFSCompoundFileDirectory(String fileName, IOContext context) throws IOException { - super(SimpleFSDirectory.this, fileName, context); - IndexInput stream = null; - try { - final File f = new File(SimpleFSDirectory.this.getDirectory(), fileName); - fd = new SimpleFSIndexInput.Descriptor(f, "r"); - stream = new SimpleFSIndexInput(fd, 0, fd.length, readBufferSize, - getReadChunkSize()); - initForRead(CompoundFileDirectory.readEntries(stream, SimpleFSDirectory.this, fileName)); - stream.close(); - } catch (IOException e) { - // throw our original exception - IOUtils.closeSafely(e, fd, stream); - } - } - - @Override - public IndexInput openInputSlice(String id, long offset, long length, int readBufferSize) throws IOException { - return new SimpleFSIndexInput(fd, offset, length, readBufferSize, getReadChunkSize()); - } - - @Override - public synchronized void close() throws IOException { - try { - fd.close(); - } finally { - super.close(); - } - } - } protected static class SimpleFSIndexInput extends BufferedIndexInput { diff --git a/lucene/src/test-framework/org/apache/lucene/store/MockCompoundFileDirectoryWrapper.java b/lucene/src/test-framework/org/apache/lucene/store/MockCompoundFileDirectoryWrapper.java deleted file mode 100644 index cc465358750..00000000000 --- a/lucene/src/test-framework/org/apache/lucene/store/MockCompoundFileDirectoryWrapper.java +++ /dev/null @@ -1,149 +0,0 @@ -package 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. - */ - -import java.io.IOException; -import java.util.Collection; - -public class MockCompoundFileDirectoryWrapper extends CompoundFileDirectory { - private final MockDirectoryWrapper parent; - private final CompoundFileDirectory delegate; - private final String name; - - public MockCompoundFileDirectoryWrapper(String name, MockDirectoryWrapper parent, CompoundFileDirectory delegate, boolean forWrite) throws IOException { - super(parent, name, IOContext.DEFAULT); - this.name = name; - this.parent = parent; - this.delegate = delegate; - // don't initialize here since we delegate everything - if not initialized a direct call will cause an assert to fail! - parent.addFileHandle(this, name, !forWrite); - } - - @Override - public Directory getDirectory() { - return delegate.getDirectory(); - } - - @Override - public String getName() { - return delegate.getName(); - } - - @Override - public synchronized void close() throws IOException { - delegate.close(); - parent.removeOpenFile(this, name); - } - - @Override - public synchronized IndexInput openInput(String id, IOContext context) throws IOException { - return delegate.openInput(id, context); - } - - @Override - public String[] listAll() { - return delegate.listAll(); - } - - @Override - public boolean fileExists(String name) { - return delegate.fileExists(name); - } - - @Override - public long fileModified(String name) throws IOException { - return delegate.fileModified(name); - } - - @Override - public void deleteFile(String name) { - delegate.deleteFile(name); - } - - @Override - public void renameFile(String from, String to) { - delegate.renameFile(from, to); - } - - @Override - public long fileLength(String name) throws IOException { - return delegate.fileLength(name); - } - - @Override - public IndexOutput createOutput(String name, IOContext context) throws IOException { - return delegate.createOutput(name, context); - } - - @Override - public void sync(Collection names) throws IOException { - delegate.sync(names); - } - - @Override - public Lock makeLock(String name) { - return delegate.makeLock(name); - } - - @Override - public void clearLock(String name) throws IOException { - delegate.clearLock(name); - } - - @Override - public void setLockFactory(LockFactory lockFactory) throws IOException { - delegate.setLockFactory(lockFactory); - } - - @Override - public LockFactory getLockFactory() { - return delegate.getLockFactory(); - } - - @Override - public String getLockID() { - return delegate.getLockID(); - } - - @Override - public String toString() { - return "MockCompoundFileDirectoryWrapper(" + super.toString() + ")"; - } - - @Override - public void copy(Directory to, String src, String dest, IOContext context) throws IOException { - delegate.copy(to, src, dest, context); - } - - @Override - public IndexInput openInputSlice(String id, long offset, long length, int readBufferSize) throws IOException { - return delegate.openInputSlice(id, offset, length, readBufferSize); - } - - @Override - public CompoundFileDirectory createCompoundOutput(String name, IOContext context) throws IOException { - return delegate.createCompoundOutput(name, context); - } - - @Override - public CompoundFileDirectory openCompoundInput(String name, IOContext context) - throws IOException { - return delegate.openCompoundInput(name, context); - } - -} diff --git a/lucene/src/test-framework/org/apache/lucene/store/MockDirectoryWrapper.java b/lucene/src/test-framework/org/apache/lucene/store/MockDirectoryWrapper.java index 4064b4605ca..3310d8f14d7 100644 --- a/lucene/src/test-framework/org/apache/lucene/store/MockDirectoryWrapper.java +++ b/lucene/src/test-framework/org/apache/lucene/store/MockDirectoryWrapper.java @@ -377,7 +377,7 @@ public class MockDirectoryWrapper extends Directory { //System.out.println(Thread.currentThread().getName() + ": MDW: create " + name); IndexOutput io = new MockIndexOutputWrapper(this, delegate.createOutput(name, LuceneTestCase.newIOContext(randomState)), name); - addFileHandle(io, name, false); + addFileHandle(io, name, Handle.Output); openFilesForWrite.add(name); // throttling REALLY slows down tests, so don't do it very often for SOMETIMES. @@ -391,8 +391,12 @@ public class MockDirectoryWrapper extends Directory { return io; } } + + private static enum Handle { + Input, Output, Slice + } - synchronized void addFileHandle(Closeable c, String name, boolean input) { + synchronized void addFileHandle(Closeable c, String name, Handle handle) { Integer v = openFiles.get(name); if (v != null) { v = Integer.valueOf(v.intValue()+1); @@ -401,7 +405,7 @@ public class MockDirectoryWrapper extends Directory { openFiles.put(name, Integer.valueOf(1)); } - openFileHandles.put(c, new RuntimeException("unclosed Index" + (input ? "Input" : "Output") + ": " + name)); + openFileHandles.put(c, new RuntimeException("unclosed Index" + handle.name() + ": " + name)); } @Override @@ -417,22 +421,10 @@ public class MockDirectoryWrapper extends Directory { } IndexInput ii = new MockIndexInputWrapper(this, name, delegate.openInput(name, LuceneTestCase.newIOContext(randomState))); - addFileHandle(ii, name, true); + addFileHandle(ii, name, Handle.Input); return ii; } - @Override - public synchronized CompoundFileDirectory openCompoundInput(String name, IOContext context) throws IOException { - maybeYield(); - return new MockCompoundFileDirectoryWrapper(name, this, delegate.openCompoundInput(name, context), false); - } - - @Override - public CompoundFileDirectory createCompoundOutput(String name, IOContext context) throws IOException { - maybeYield(); - return new MockCompoundFileDirectoryWrapper(name, this, delegate.createCompoundOutput(name, context), true); - } - /** Provided for testing purposes. Use sizeInBytes() instead. */ public synchronized final long getRecomputedSizeInBytes() throws IOException { if (!(delegate instanceof RAMDirectory)) @@ -658,5 +650,50 @@ public class MockDirectoryWrapper extends Directory { // randomize the IOContext here? delegate.copy(to, src, dest, context); } - + + @Override + public IndexInputSlicer createSlicer(final String name, IOContext context) + throws IOException { + maybeYield(); + if (!delegate.fileExists(name)) + throw new FileNotFoundException(name); + // cannot open a file for input if it's still open for + // output, except for segments.gen and segments_N + if (openFilesForWrite.contains(name) && !name.startsWith("segments")) { + throw fillOpenTrace(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for writing"), name, false); + } + + final IndexInputSlicer delegateHandle = delegate.createSlicer(name, context); + final IndexInputSlicer handle = new IndexInputSlicer() { + + private boolean isClosed; + @Override + public void close() throws IOException { + if (!isClosed) { + delegateHandle.close(); + MockDirectoryWrapper.this.removeOpenFile(this, name); + isClosed = true; + } + } + + @Override + public IndexInput openSlice(long offset, long length) throws IOException { + maybeYield(); + IndexInput ii = new MockIndexInputWrapper(MockDirectoryWrapper.this, name, delegateHandle.openSlice(offset, length)); + addFileHandle(ii, name, Handle.Input); + return ii; + } + + @Override + public IndexInput openFullSlice() throws IOException { + maybeYield(); + IndexInput ii = new MockIndexInputWrapper(MockDirectoryWrapper.this, name, delegateHandle.openFullSlice()); + addFileHandle(ii, name, Handle.Input); + return ii; + } + + }; + addFileHandle(handle, name, Handle.Slice); + return handle; + } } diff --git a/lucene/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java b/lucene/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java index 165aee9f3c7..9aa5eba80fa 100644 --- a/lucene/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java +++ b/lucene/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java @@ -548,7 +548,7 @@ public class TestBackwardsCompatibility extends LuceneTestCase { // figure out which field number corresponds to // "content", and then set our expected file names below // accordingly: - CompoundFileDirectory cfsReader = dir.openCompoundInput("_0.cfs", newIOContext(random)); + CompoundFileDirectory cfsReader = new CompoundFileDirectory(dir, "_0.cfs", newIOContext(random), false); FieldInfos fieldInfos = new FieldInfos(cfsReader, "_0.fnm"); int contentFieldIndex = -1; for (FieldInfo fi : fieldInfos) { diff --git a/lucene/src/test/org/apache/lucene/index/TestCompoundFile.java b/lucene/src/test/org/apache/lucene/index/TestCompoundFile.java index a3a4a440970..850bd8123b2 100644 --- a/lucene/src/test/org/apache/lucene/index/TestCompoundFile.java +++ b/lucene/src/test/org/apache/lucene/index/TestCompoundFile.java @@ -182,11 +182,11 @@ public class TestCompoundFile extends LuceneTestCase for (int i=0; iBmXiat@8;>` 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 literal 6103 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 WZ$vQU*bZQrrwFEC!ZF_i0R98Xo^750 diff --git a/lucene/src/test/org/apache/lucene/index/index.34.nocfs.zip b/lucene/src/test/org/apache/lucene/index/index.34.nocfs.zip index 6b924dcdbff221cafd050704f7a68d01ef73c58e..935d6a1c0f48d68d0808791f2c191e903ec13070 100644 GIT binary patch literal 12145 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