Property changes on: . ___________________________________________________________________ Modified: svn:mergeinfo Merged /lucene/dev/trunk:r1128830 Property changes on: solr ___________________________________________________________________ Modified: svn:mergeinfo Merged /lucene/dev/trunk/solr:r1128830 Property changes on: lucene ___________________________________________________________________ Modified: svn:mergeinfo Merged /lucene/dev/trunk/lucene:r1128830 Index: lucene/contrib/misc/src/java/org/apache/lucene/store/NRTCachingDirectory.java =================================================================== --- lucene/contrib/misc/src/java/org/apache/lucene/store/NRTCachingDirectory.java (revision 1128868) +++ lucene/contrib/misc/src/java/org/apache/lucene/store/NRTCachingDirectory.java (working copy) @@ -281,7 +281,7 @@ in = cache.openInput(fileName); in.copyBytes(out, in.length()); } finally { - IOUtils.closeSafely(in, out); + IOUtils.closeSafely(false, in, out); } synchronized(this) { cache.deleteFile(fileName); Property changes on: lucene/backwards ___________________________________________________________________ Modified: svn:mergeinfo Merged /lucene/dev/trunk/lucene/backwards:r1128830 Index: lucene/CHANGES.txt =================================================================== --- lucene/CHANGES.txt (revision 1128868) +++ lucene/CHANGES.txt (working copy) @@ -8,6 +8,12 @@ lock file is not stored in the index has changed. This means you will see a different lucene-XXX-write.lock in your lock directory. (Robert Muir, Uwe Schindler, Mike McCandless) + +Bug fixes +* LUCENE-3147,LUCENE-3152: Fixed open file handles leaks in many places in the + code. Now MockDirectoryWrapper (in test-framework) tracks all open files, + including locks, and fails if the test fails to release all of them. + (Mike McCandless, Robert Muir, Shai Erera, Simon Willnauer) ======================= Lucene 3.2.0 ======================= Index: lucene/src/test/org/apache/lucene/index/TestIndexReader.java =================================================================== --- lucene/src/test/org/apache/lucene/index/TestIndexReader.java (revision 1128868) +++ lucene/src/test/org/apache/lucene/index/TestIndexReader.java (working copy) @@ -1642,6 +1642,7 @@ r2.close(); assertTrue(ints == ints2); + writer.close(); dir.close(); } @@ -1689,6 +1690,7 @@ assertTrue(subs[1] instanceof ReadOnlySegmentReader); assertTrue(ints == ints2); + writer.close(); dir.close(); } Index: lucene/src/test/org/apache/lucene/index/TestIndexWriter.java =================================================================== --- lucene/src/test/org/apache/lucene/index/TestIndexWriter.java (revision 1128868) +++ lucene/src/test/org/apache/lucene/index/TestIndexWriter.java (working copy) @@ -2422,6 +2422,10 @@ allowInterrupt = true; } } catch (ThreadInterruptedException re) { + if (VERBOSE) { + System.out.println("TEST: got interrupt"); + re.printStackTrace(System.out); + } Throwable e = re.getCause(); assertTrue(e instanceof InterruptedException); if (finish) { @@ -2873,7 +2877,7 @@ // or, at most the write.lock file final int extraFileCount; if (files.length == 1) { - assertEquals("write.lock", files[0]); + assertTrue(files[0].endsWith("write.lock")); extraFileCount = 1; } else { assertEquals(0, files.length); Index: lucene/src/test/org/apache/lucene/index/TestCrash.java =================================================================== --- lucene/src/test/org/apache/lucene/index/TestCrash.java (revision 1128868) +++ lucene/src/test/org/apache/lucene/index/TestCrash.java (working copy) @@ -179,6 +179,7 @@ reader = IndexReader.open(dir, false); assertEquals(157, reader.numDocs()); reader.close(); + dir.clearCrash(); dir.close(); } Index: lucene/src/test/org/apache/lucene/index/TestIndexWriterWithThreads.java =================================================================== --- lucene/src/test/org/apache/lucene/index/TestIndexWriterWithThreads.java (revision 1128868) +++ lucene/src/test/org/apache/lucene/index/TestIndexWriterWithThreads.java (working copy) @@ -63,6 +63,10 @@ writer.updateDocument(new Term("id", ""+(idUpto++)), doc); addCount++; } catch (IOException ioe) { + if (VERBOSE) { + System.out.println("TEST: expected exc:"); + ioe.printStackTrace(System.out); + } //System.out.println(Thread.currentThread().getName() + ": hit exc"); //ioe.printStackTrace(System.out); if (ioe.getMessage().startsWith("fake disk full at") || @@ -209,13 +213,19 @@ int NUM_THREADS = 3; for(int iter=0;iter<2;iter++) { + if (VERBOSE) { + System.out.println("TEST: iter=" + iter); + } MockDirectoryWrapper dir = newDirectory(); - IndexWriterConfig conf = newIndexWriterConfig( TEST_VERSION_CURRENT, - new MockAnalyzer(random)).setMaxBufferedDocs(2).setMergeScheduler(new ConcurrentMergeScheduler()).setMergePolicy(newLogMergePolicy(4)); + IndexWriterConfig conf = newIndexWriterConfig(TEST_VERSION_CURRENT, + new MockAnalyzer(random)).setMaxBufferedDocs(2) + .setMergeScheduler(new ConcurrentMergeScheduler()) + .setMergePolicy(newLogMergePolicy(4)); // We expect disk full exceptions in the merge threads ((ConcurrentMergeScheduler) conf.getMergeScheduler()).setSuppressExceptions(); IndexWriter writer = new IndexWriter(dir, conf); - + writer.setInfoStream(VERBOSE ? System.out : null); + IndexerThread[] threads = new IndexerThread[NUM_THREADS]; for(int i=0;i fields() { Index: lucene/src/java/org/apache/lucene/index/SegmentInfos.java =================================================================== --- lucene/src/java/org/apache/lucene/index/SegmentInfos.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/SegmentInfos.java (working copy) @@ -23,6 +23,7 @@ import org.apache.lucene.store.ChecksumIndexOutput; import org.apache.lucene.store.ChecksumIndexInput; import org.apache.lucene.store.NoSuchDirectoryException; +import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.ThreadInterruptedException; import java.io.FileNotFoundException; @@ -390,18 +391,14 @@ } segnOutput.writeStringStringMap(userData); segnOutput.prepareCommit(); - success = true; pendingSegnOutput = segnOutput; + success = true; } finally { if (!success) { // We hit an exception above; try to close the file // but suppress any exception: + IOUtils.closeSafely(true, segnOutput); try { - segnOutput.close(); - } catch (Throwable t) { - // Suppress so we keep throwing the original exception - } - try { // Try not to leave a truncated segments_N file in // the index: directory.deleteFile(segmentFileName); @@ -943,6 +940,8 @@ } finally { genOutput.close(); } + } catch (ThreadInterruptedException t) { + throw t; } catch (Throwable t) { // It's OK if we fail to write this file since it's // used only as one of the retry fallbacks. Index: lucene/src/java/org/apache/lucene/index/TermsHash.java =================================================================== --- lucene/src/java/org/apache/lucene/index/TermsHash.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/TermsHash.java (working copy) @@ -64,9 +64,13 @@ @Override public void abort() { - consumer.abort(); - if (nextTermsHash != null) - nextTermsHash.abort(); + try { + consumer.abort(); + } finally { + if (nextTermsHash != null) { + nextTermsHash.abort(); + } + } } @Override Index: lucene/src/java/org/apache/lucene/index/SegmentMerger.java =================================================================== --- lucene/src/java/org/apache/lucene/index/SegmentMerger.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/SegmentMerger.java (working copy) @@ -29,6 +29,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.ReaderUtil; /** @@ -467,8 +468,13 @@ mergeTermInfos(fieldsConsumer); } finally { - fieldsConsumer.finish(); - if (queue != null) queue.close(); + try { + fieldsConsumer.finish(); + } finally { + if (queue != null) { + queue.close(); + } + } } } @@ -530,7 +536,6 @@ } int df = appendPostings(termsConsumer, match, matchSize); // add new TermInfo - checkAbort.work(df/3.0); while (matchSize > 0) { @@ -628,6 +633,7 @@ byte[] normBuffer = null; IndexOutput output = null; + boolean success = false; try { int numFieldInfos = fieldInfos.size(); for (int i = 0; i < numFieldInfos; i++) { @@ -659,10 +665,9 @@ } } } + success = true; } finally { - if (output != null) { - output.close(); - } + IOUtils.closeSafely(!success, output); } } Index: lucene/src/java/org/apache/lucene/index/TermsHashPerField.java =================================================================== --- lucene/src/java/org/apache/lucene/index/TermsHashPerField.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/TermsHashPerField.java (working copy) @@ -525,9 +525,13 @@ @Override void finish() throws IOException { - consumer.finish(); - if (nextPerField != null) - nextPerField.finish(); + try { + consumer.finish(); + } finally { + if (nextPerField != null) { + nextPerField.finish(); + } + } } /** Called when postings hash is too small (> 50% Index: lucene/src/java/org/apache/lucene/index/DocFieldProcessor.java =================================================================== --- lucene/src/java/org/apache/lucene/index/DocFieldProcessor.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/DocFieldProcessor.java (working copy) @@ -55,6 +55,7 @@ childThreadsAndFields.put(perThread.consumer, perThread.fields()); perThread.trimFields(state); } + fieldsWriter.flush(state); consumer.flush(childThreadsAndFields, state); @@ -68,8 +69,11 @@ @Override public void abort() { - fieldsWriter.abort(); - consumer.abort(); + try { + fieldsWriter.abort(); + } finally { + consumer.abort(); + } } @Override Index: lucene/src/java/org/apache/lucene/index/FreqProxTermsWriter.java =================================================================== --- lucene/src/java/org/apache/lucene/index/FreqProxTermsWriter.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/FreqProxTermsWriter.java (working copy) @@ -69,7 +69,6 @@ Collection fields = entry.getValue(); - for (final TermsHashConsumerPerField i : fields) { final FreqProxTermsWriterPerField perField = (FreqProxTermsWriterPerField) i; if (perField.termsHashPerField.numPostings > 0) Index: lucene/src/java/org/apache/lucene/index/FormatPostingsTermsWriter.java =================================================================== --- lucene/src/java/org/apache/lucene/index/FormatPostingsTermsWriter.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/FormatPostingsTermsWriter.java (working copy) @@ -17,9 +17,10 @@ * limitations under the License. */ +import java.io.Closeable; import java.io.IOException; -final class FormatPostingsTermsWriter extends FormatPostingsTermsConsumer { +final class FormatPostingsTermsWriter extends FormatPostingsTermsConsumer implements Closeable { final FormatPostingsFieldsWriter parent; final FormatPostingsDocsWriter docsWriter; @@ -27,7 +28,6 @@ FieldInfo fieldInfo; FormatPostingsTermsWriter(SegmentWriteState state, FormatPostingsFieldsWriter parent) throws IOException { - super(); this.parent = parent; termsOut = parent.termsOut; docsWriter = new FormatPostingsDocsWriter(state, this); @@ -67,7 +67,7 @@ void finish() { } - void close() throws IOException { + public void close() throws IOException { docsWriter.close(); } } Index: lucene/src/java/org/apache/lucene/index/SegmentMergeInfo.java =================================================================== --- lucene/src/java/org/apache/lucene/index/SegmentMergeInfo.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/SegmentMergeInfo.java (working copy) @@ -79,10 +79,13 @@ } final void close() throws IOException { - termEnum.close(); - if (postings != null) { - postings.close(); + try { + termEnum.close(); + } finally { + if (postings != null) { + postings.close(); + } + } } } -} Index: lucene/src/java/org/apache/lucene/index/TermVectorsTermsWriter.java =================================================================== --- lucene/src/java/org/apache/lucene/index/TermVectorsTermsWriter.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/TermVectorsTermsWriter.java (working copy) @@ -53,9 +53,7 @@ if (tvx != null) { // At least one doc in this run had term vectors enabled fill(state.numDocs); - tvx.close(); - tvf.close(); - tvd.close(); + IOUtils.closeSafely(false, tvx, tvf, tvd); tvx = tvd = tvf = null; assert state.segmentName != null; String idxName = IndexFileNames.segmentFileName(state.segmentName, IndexFileNames.VECTORS_INDEX_EXTENSION); @@ -114,20 +112,26 @@ synchronized void initTermVectorsWriter() throws IOException { if (tvx == null) { - - // If we hit an exception while init'ing the term - // vector output files, we must abort this segment - // because those files will be in an unknown - // state: - hasVectors = true; - tvx = docWriter.directory.createOutput(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_INDEX_EXTENSION)); - tvd = docWriter.directory.createOutput(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_DOCUMENTS_EXTENSION)); - tvf = docWriter.directory.createOutput(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_FIELDS_EXTENSION)); - - tvx.writeInt(TermVectorsReader.FORMAT_CURRENT); - tvd.writeInt(TermVectorsReader.FORMAT_CURRENT); - tvf.writeInt(TermVectorsReader.FORMAT_CURRENT); - + boolean success = false; + try { + // If we hit an exception while init'ing the term + // vector output files, we must abort this segment + // because those files will be in an unknown + // state: + hasVectors = true; + tvx = docWriter.directory.createOutput(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_INDEX_EXTENSION)); + tvd = docWriter.directory.createOutput(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_DOCUMENTS_EXTENSION)); + tvf = docWriter.directory.createOutput(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_FIELDS_EXTENSION)); + + tvx.writeInt(TermVectorsReader.FORMAT_CURRENT); + tvd.writeInt(TermVectorsReader.FORMAT_CURRENT); + tvf.writeInt(TermVectorsReader.FORMAT_CURRENT); + success = true; + } finally { + if (!success) { + IOUtils.closeSafely(true, tvx, tvd, tvf); + } + } lastDocID = 0; } } @@ -172,21 +176,27 @@ public void abort() { hasVectors = false; try { - IOUtils.closeSafely(tvx, tvd, tvf); - } catch (IOException ignored) { + IOUtils.closeSafely(true, tvx, tvd, tvf); + } catch (IOException e) { + // cannot happen since we suppress exceptions + throw new RuntimeException(e); } + try { docWriter.directory.deleteFile(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_INDEX_EXTENSION)); } catch (IOException ignored) { } + try { docWriter.directory.deleteFile(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_DOCUMENTS_EXTENSION)); } catch (IOException ignored) { } + try { docWriter.directory.deleteFile(IndexFileNames.segmentFileName(docWriter.getSegment(), IndexFileNames.VECTORS_FIELDS_EXTENSION)); } catch (IOException ignored) { } + tvx = tvd = tvf = null; lastDocID = 0; } Index: lucene/src/java/org/apache/lucene/index/TermsHashPerThread.java =================================================================== --- lucene/src/java/org/apache/lucene/index/TermsHashPerThread.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/TermsHashPerThread.java (working copy) @@ -63,9 +63,13 @@ @Override synchronized public void abort() { reset(true); - consumer.abort(); - if (nextPerThread != null) - nextPerThread.abort(); + try { + consumer.abort(); + } finally { + if (nextPerThread != null) { + nextPerThread.abort(); + } + } } @Override Index: lucene/src/java/org/apache/lucene/index/DocumentsWriter.java =================================================================== --- lucene/src/java/org/apache/lucene/index/DocumentsWriter.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/DocumentsWriter.java (working copy) @@ -400,35 +400,41 @@ try { // Forcefully remove waiting ThreadStates from line - waitQueue.abort(); + try { + waitQueue.abort(); + } catch (Throwable t) { + } // Wait for all other threads to finish with // DocumentsWriter: - waitIdle(); - - if (infoStream != null) { - message("docWriter: abort waitIdle done"); - } - - assert 0 == waitQueue.numWaiting: "waitQueue.numWaiting=" + waitQueue.numWaiting; - - waitQueue.waitingBytes = 0; - - pendingDeletes.clear(); - - for (DocumentsWriterThreadState threadState : threadStates) + try { + waitIdle(); + } finally { + if (infoStream != null) { + message("docWriter: abort waitIdle done"); + } + + assert 0 == waitQueue.numWaiting: "waitQueue.numWaiting=" + waitQueue.numWaiting; + waitQueue.waitingBytes = 0; + + pendingDeletes.clear(); + + for (DocumentsWriterThreadState threadState : threadStates) { + try { + threadState.consumer.abort(); + } catch (Throwable t) { + } + } + try { - threadState.consumer.abort(); + consumer.abort(); } catch (Throwable t) { } - - try { - consumer.abort(); - } catch (Throwable t) { + + // Reset all postings data + doAfterFlush(); } - // Reset all postings data - doAfterFlush(); success = true; } finally { aborting = false; Index: lucene/src/java/org/apache/lucene/index/TermInfosWriter.java =================================================================== --- lucene/src/java/org/apache/lucene/index/TermInfosWriter.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/TermInfosWriter.java (working copy) @@ -18,9 +18,11 @@ */ +import java.io.Closeable; import java.io.IOException; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.Directory; +import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.UnicodeUtil; import org.apache.lucene.util.ArrayUtil; @@ -28,7 +30,7 @@ /** This stores a monotonically increasing set of pairs in a Directory. A TermInfos can be written once, in order. */ -final class TermInfosWriter { +final class TermInfosWriter implements Closeable { /** The file format version, a negative number. */ public static final int FORMAT = -3; @@ -83,8 +85,16 @@ int interval) throws IOException { initialize(directory, segment, fis, interval, false); - other = new TermInfosWriter(directory, segment, fis, interval, true); - other.other = this; + boolean success = false; + try { + other = new TermInfosWriter(directory, segment, fis, interval, true); + other.other = this; + success = true; + } finally { + if (!success) { + IOUtils.closeSafely(true, output, other); + } + } } private TermInfosWriter(Directory directory, String segment, FieldInfos fis, @@ -98,12 +108,20 @@ fieldInfos = fis; isIndex = isi; output = directory.createOutput(segment + (isIndex ? ".tii" : ".tis")); - output.writeInt(FORMAT_CURRENT); // write format - output.writeLong(0); // leave space for size - output.writeInt(indexInterval); // write indexInterval - output.writeInt(skipInterval); // write skipInterval - output.writeInt(maxSkipLevels); // write maxSkipLevels - assert initUTF16Results(); + boolean success = false; + try { + output.writeInt(FORMAT_CURRENT); // write format + output.writeLong(0); // leave space for size + output.writeInt(indexInterval); // write indexInterval + output.writeInt(skipInterval); // write skipInterval + output.writeInt(maxSkipLevels); // write maxSkipLevels + assert initUTF16Results(); + success = true; + } finally { + if (!success) { + IOUtils.closeSafely(true, output); + } + } } void add(Term term, TermInfo ti) throws IOException { @@ -216,13 +234,20 @@ } /** Called to complete TermInfos creation. */ - void close() throws IOException { - output.seek(4); // write size after format - output.writeLong(size); - output.close(); + public void close() throws IOException { + try { + output.seek(4); // write size after format + output.writeLong(size); + } finally { + try { + output.close(); + } finally { + if (!isIndex) { + other.close(); + } + } + } - if (!isIndex) - other.close(); } } Index: lucene/src/java/org/apache/lucene/index/IndexWriter.java =================================================================== --- lucene/src/java/org/apache/lucene/index/IndexWriter.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/IndexWriter.java (working copy) @@ -4083,48 +4083,48 @@ private final synchronized void closeMergeReaders(MergePolicy.OneMerge merge, boolean suppressExceptions) throws IOException { final int numSegments = merge.readers.size(); - if (suppressExceptions) { - // Suppress any new exceptions so we throw the - // original cause - boolean anyChanges = false; - for (int i=0;i> endChildThreadsAndFields = new HashMap>(); for (Map.Entry> entry : threadsAndFields.entrySet() ) { - - DocInverterPerThread perThread = (DocInverterPerThread) entry.getKey(); Collection childFields = new HashSet(); @@ -75,8 +73,11 @@ @Override void abort() { - consumer.abort(); - endConsumer.abort(); + try { + consumer.abort(); + } finally { + endConsumer.abort(); + } } @Override Index: lucene/src/java/org/apache/lucene/index/NormsWriter.java =================================================================== --- lucene/src/java/org/apache/lucene/index/NormsWriter.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/index/NormsWriter.java (working copy) @@ -27,6 +27,7 @@ import org.apache.lucene.store.IndexOutput; import org.apache.lucene.search.Similarity; +import org.apache.lucene.util.IOUtils; // TODO FI: norms could actually be stored as doc store @@ -90,7 +91,7 @@ final String normsFileName = IndexFileNames.segmentFileName(state.segmentName, IndexFileNames.NORMS_EXTENSION); IndexOutput normsOut = state.directory.createOutput(normsFileName); - + boolean success = false; try { normsOut.writeBytes(SegmentNorms.NORMS_HEADER, 0, SegmentNorms.NORMS_HEADER.length); @@ -165,9 +166,9 @@ assert 4+normCount*state.numDocs == normsOut.getFilePointer() : ".nrm file size mismatch: expected=" + (4+normCount*state.numDocs) + " actual=" + normsOut.getFilePointer(); } - + success = true; } finally { - normsOut.close(); + IOUtils.closeSafely(!success, normsOut); } } } Index: lucene/src/java/org/apache/lucene/util/IOUtils.java =================================================================== --- lucene/src/java/org/apache/lucene/util/IOUtils.java (revision 1128868) +++ lucene/src/java/org/apache/lucene/util/IOUtils.java (working copy) @@ -47,44 +47,113 @@ * @param objects objects to call close() on */ public static void closeSafely(E priorException, Closeable... objects) throws E, IOException { - IOException firstIOE = null; + Throwable th = null; for (Closeable object : objects) { try { - if (object != null) + if (object != null) { object.close(); - } catch (IOException ioe) { - if (firstIOE == null) - firstIOE = ioe; + } + } catch (Throwable t) { + if (th == null) { + th = t; + } } } - if (priorException != null) + if (priorException != null) { throw priorException; - else if (firstIOE != null) - throw firstIOE; + } else if (th != null) { + if (th instanceof IOException) throw (IOException) th; + if (th instanceof RuntimeException) throw (RuntimeException) th; + if (th instanceof Error) throw (Error) th; + throw new RuntimeException(th); + } } + /** @see #closeSafely(Exception, Closeable...) */ + public static void closeSafely(E priorException, Iterable objects) throws E, IOException { + Throwable th = null; + + for (Closeable object : objects) { + try { + if (object != null) { + object.close(); + } + } catch (Throwable t) { + if (th == null) { + th = t; + } + } + } + + if (priorException != null) { + throw priorException; + } else if (th != null) { + if (th instanceof IOException) throw (IOException) th; + if (th instanceof RuntimeException) throw (RuntimeException) th; + if (th instanceof Error) throw (Error) th; + throw new RuntimeException(th); + } + } + /** - *

Closes all given Closeables, suppressing all thrown exceptions. Some of the Closeables - * may be null, they are ignored. After everything is closed, method either throws the first of suppressed exceptions, - * or completes normally.

- * @param objects objects to call close() on + * Closes all given Closeables, suppressing all thrown exceptions. + * Some of the Closeables may be null, they are ignored. After + * everything is closed, and if {@code suppressExceptions} is {@code false}, + * method either throws the first of suppressed exceptions, or completes + * normally. + * + * @param suppressExceptions + * if true then exceptions that occur during close() are suppressed + * @param objects + * objects to call close() on */ - public static void closeSafely(Closeable... objects) throws IOException { - IOException firstIOE = null; + public static void closeSafely(boolean suppressExceptions, Closeable... objects) throws IOException { + Throwable th = null; for (Closeable object : objects) { try { - if (object != null) + if (object != null) { object.close(); - } catch (IOException ioe) { - if (firstIOE == null) - firstIOE = ioe; + } + } catch (Throwable t) { + if (th == null) + th = t; } } - if (firstIOE != null) - throw firstIOE; + if (th != null && !suppressExceptions) { + if (th instanceof IOException) throw (IOException) th; + if (th instanceof RuntimeException) throw (RuntimeException) th; + if (th instanceof Error) throw (Error) th; + throw new RuntimeException(th); + } } + + /** + * @see #closeSafely(boolean, Closeable...) + */ + public static void closeSafely(boolean suppressExceptions, Iterable objects) throws IOException { + Throwable th = null; + + for (Closeable object : objects) { + try { + if (object != null) { + object.close(); + } + } catch (Throwable t) { + if (th == null) + th = t; + } + } + + if (th != null && !suppressExceptions) { + if (th instanceof IOException) throw (IOException) th; + if (th instanceof RuntimeException) throw (RuntimeException) th; + if (th instanceof Error) throw (Error) th; + throw new RuntimeException(th); + } + } + } Index: lucene/src/test-framework/org/apache/lucene/store/MockDirectoryWrapper.java =================================================================== --- lucene/src/test-framework/org/apache/lucene/store/MockDirectoryWrapper.java (revision 1128868) +++ lucene/src/test-framework/org/apache/lucene/store/MockDirectoryWrapper.java (working copy) @@ -67,24 +67,25 @@ boolean trackDiskUsage = false; private Set unSyncedFiles; private Set createdFiles; - Set openFilesForWrite = new HashSet(); + private Set openFilesForWrite = new HashSet(); + Set openLocks = Collections.synchronizedSet(new HashSet()); volatile boolean crashed; private ThrottledIndexOutput throttledOutput; private Throttling throttling = Throttling.SOMETIMES; // use this for tracking files for crash. // additionally: provides debugging information in case you leave one open - Map openFileHandles = Collections.synchronizedMap(new IdentityHashMap()); + private Map openFileHandles = Collections.synchronizedMap(new IdentityHashMap()); // NOTE: we cannot initialize the Map here due to the // order in which our constructor actually does this // member initialization vs when it calls super. It seems // like super is called, then our members are initialized: - Map openFiles; + private Map openFiles; // Only tracked if noDeleteOpenFile is true: if an attempt // is made to delete an open file, we enroll it here. - Set openFilesDeleted; + private Set openFilesDeleted; private synchronized void init() { if (openFiles == null) { @@ -106,6 +107,12 @@ this.randomState = new Random(random.nextInt()); this.throttledOutput = new ThrottledIndexOutput(ThrottledIndexOutput .mBitsToBytes(40 + randomState.nextInt(10)), 5 + randomState.nextInt(5), null); + // force wrapping of lockfactory + try { + setLockFactory(new MockLockFactoryWrapper(this, delegate.getLockFactory())); + } catch (IOException e) { + throw new RuntimeException(e); + } init(); } @@ -137,7 +144,7 @@ SOMETIMES, /** never throttle output */ NEVER - }; + } public void setThrottling(Throttling throttling) { this.throttling = throttling; @@ -218,6 +225,7 @@ public synchronized void clearCrash() throws IOException { crashed = false; + openLocks.clear(); } public void setMaxSizeInBytes(long maxSize) { @@ -372,9 +380,10 @@ ramdir.fileMap.put(name, file); } } + //System.out.println(Thread.currentThread().getName() + ": MDW: create " + name); IndexOutput io = new MockIndexOutputWrapper(this, delegate.createOutput(name), name); - openFileHandles.put(io, new RuntimeException("unclosed IndexOutput")); + addFileHandle(io, name, false); openFilesForWrite.add(name); // throttling REALLY slows down tests, so don't do it very often for SOMETIMES. @@ -389,6 +398,18 @@ } } + private void addFileHandle(Closeable c, String name, boolean input) { + Integer v = openFiles.get(name); + if (v != null) { + v = Integer.valueOf(v.intValue()+1); + openFiles.put(name, v); + } else { + openFiles.put(name, Integer.valueOf(1)); + } + + openFileHandles.put(c, new RuntimeException("unclosed Index" + (input ? "Input" : "Output") + ": " + name)); + } + @Override public synchronized IndexInput openInput(String name) throws IOException { maybeYield(); @@ -401,16 +422,8 @@ throw fillOpenTrace(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for writing"), name, false); } - if (openFiles.containsKey(name)) { - Integer v = openFiles.get(name); - v = Integer.valueOf(v.intValue()+1); - openFiles.put(name, v); - } else { - openFiles.put(name, Integer.valueOf(1)); - } - IndexInput ii = new MockIndexInputWrapper(this, name, delegate.openInput(name)); - openFileHandles.put(ii, new RuntimeException("unclosed IndexInput")); + addFileHandle(ii, name, true); return ii; } @@ -457,6 +470,9 @@ // super() does not throw IOException currently: throw new RuntimeException("MockDirectoryWrapper: cannot close: there are still open files: " + openFiles, cause); } + if (noDeleteOpenFile && openLocks.size() > 0) { + throw new RuntimeException("MockDirectoryWrapper: cannot close: there are still open locks: " + openLocks); + } open = false; if (checkIndexOnClose && IndexReader.indexExists(this)) { if (LuceneTestCase.VERBOSE) { @@ -467,6 +483,31 @@ delegate.close(); } + private synchronized void removeOpenFile(Closeable c, String name) { + Integer v = openFiles.get(name); + // Could be null when crash() was called + if (v != null) { + if (v.intValue() == 1) { + openFiles.remove(name); + openFilesDeleted.remove(name); + } else { + v = Integer.valueOf(v.intValue()-1); + openFiles.put(name, v); + } + } + + openFileHandles.remove(c); + } + + public synchronized void removeIndexOutput(IndexOutput out, String name) { + openFilesForWrite.remove(name); + removeOpenFile(out, name); + } + + public synchronized void removeIndexInput(IndexInput in, String name) { + removeOpenFile(in, name); + } + boolean open = true; public synchronized boolean isOpen() { Index: lucene/src/test-framework/org/apache/lucene/store/MockIndexInputWrapper.java =================================================================== --- lucene/src/test-framework/org/apache/lucene/store/MockIndexInputWrapper.java (revision 1128868) +++ lucene/src/test-framework/org/apache/lucene/store/MockIndexInputWrapper.java (working copy) @@ -31,8 +31,7 @@ private IndexInput delegate; private boolean isClone; - /** Construct an empty output buffer. - * @throws IOException */ + /** Construct an empty output buffer. */ public MockIndexInputWrapper(MockDirectoryWrapper dir, String name, IndexInput delegate) { this.name = name; this.dir = dir; @@ -41,24 +40,17 @@ @Override public void close() throws IOException { - delegate.close(); - // Pending resolution on LUCENE-686 we may want to - // remove the conditional check so we also track that - // all clones get closed: - if (!isClone) { - synchronized(dir) { - Integer v = dir.openFiles.get(name); - // Could be null when MockRAMDirectory.crash() was called - if (v != null) { - if (v.intValue() == 1) { - dir.openFiles.remove(name); - dir.openFilesDeleted.remove(name); - } else { - v = Integer.valueOf(v.intValue()-1); - dir.openFiles.put(name, v); - } - } - dir.openFileHandles.remove(this); + try { + // turn on the following to look for leaks closing inputs, + // after fixing TestTransactions + // dir.maybeThrowDeterministicException(); + } finally { + delegate.close(); + // Pending resolution on LUCENE-686 we may want to + // remove the conditional check so we also track that + // all clones get closed: + if (!isClone) { + dir.removeIndexInput(this, name); } } } Index: lucene/src/test-framework/org/apache/lucene/store/MockIndexOutputWrapper.java =================================================================== --- lucene/src/test-framework/org/apache/lucene/store/MockIndexOutputWrapper.java (revision 1128868) +++ lucene/src/test-framework/org/apache/lucene/store/MockIndexOutputWrapper.java (working copy) @@ -57,10 +57,7 @@ dir.maxUsedSize = size; } } - synchronized(dir) { - dir.openFileHandles.remove(this); - dir.openFilesForWrite.remove(name); - } + dir.removeIndexOutput(this, name); } } Index: lucene/src/test-framework/org/apache/lucene/util/ThrottledIndexOutput.java =================================================================== --- lucene/src/test-framework/org/apache/lucene/util/ThrottledIndexOutput.java (revision 1128868) +++ lucene/src/test-framework/org/apache/lucene/util/ThrottledIndexOutput.java (working copy) @@ -73,9 +73,11 @@ @Override public void close() throws IOException { - sleep(closeDelayMillis + getDelay(true)); - delegate.close(); - + try { + sleep(closeDelayMillis + getDelay(true)); + } finally { + delegate.close(); + } } @Override Index: lucene/src/test-framework/org/apache/lucene/util/LuceneTestCase.java =================================================================== --- lucene/src/test-framework/org/apache/lucene/util/LuceneTestCase.java (revision 1128868) +++ lucene/src/test-framework/org/apache/lucene/util/LuceneTestCase.java (working copy) @@ -318,6 +318,7 @@ if (ste.getClassName().indexOf("org.apache.lucene") == -1) break; System.err.println("\t" + ste); } + fail("could not remove temp dir: " + entry.getKey()); } } } @@ -845,7 +846,10 @@ clazz = Class.forName(fsdirClass).asSubclass(FSDirectory.class); } - MockDirectoryWrapper dir = new MockDirectoryWrapper(random, newFSDirectoryImpl(clazz, f, lf)); + MockDirectoryWrapper dir = new MockDirectoryWrapper(random, newFSDirectoryImpl(clazz, f)); + if (lf != null) { + dir.setLockFactory(lf); + } stores.put(dir, Thread.currentThread().getStackTrace()); return dir; } catch (Exception e) { @@ -979,7 +983,7 @@ } private static Directory newFSDirectoryImpl( - Class clazz, File file, LockFactory lockFactory) + Class clazz, File file) throws IOException { FSDirectory d = null; try { @@ -990,9 +994,6 @@ } catch (Exception e) { d = FSDirectory.open(file); } - if (lockFactory != null) { - d.setLockFactory(lockFactory); - } return d; } @@ -1014,7 +1015,7 @@ tmpFile.delete(); tmpFile.mkdir(); registerTempFile(tmpFile); - return newFSDirectoryImpl(clazz.asSubclass(FSDirectory.class), tmpFile, null); + return newFSDirectoryImpl(clazz.asSubclass(FSDirectory.class), tmpFile); } // try empty ctor