blob: 87ff0ef493e0a16b680741d8b7c0bb06cfa3a542 [file] [log] [blame]
Index: src/test/org/apache/lucene/index/TestCrashCausesCorruptIndex.java
===================================================================
--- src/test/org/apache/lucene/index/TestCrashCausesCorruptIndex.java (revision 0)
+++ src/test/org/apache/lucene/index/TestCrashCausesCorruptIndex.java (revision 0)
@@ -0,0 +1,362 @@
+/**
+ *
+ */
+package org.apache.lucene.index;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Fieldable;
+import org.apache.lucene.document.Field.Index;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.MergeScheduler;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.store.LockFactory;
+import org.apache.lucene.store.SimpleFSLockFactory;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.Version;
+import org.apache.lucene.util._TestUtil;
+
+/**
+ * @author Ken McCracken
+ *
+ */
+public class TestCrashCausesCorruptIndex extends LuceneTestCase {
+
+ File path;
+ Version version = Version.LUCENE_35;
+ Analyzer analyzer = new StandardAnalyzer(version);
+
+ /**
+ * This test fails.
+ *
+ * @throws Exception
+ */
+ public void testCrashCorruptsIndexing() throws Exception {
+ path = _TestUtil.getTempDir("testCrashCorruptsIndexing");
+
+ indexAndCrashOnCreateOutputSegments2();
+
+ indexAfterRestart();
+
+ searchForFleas(3);
+ }
+
+ /**
+ * This test passes.
+ *
+ * @throws Exception
+ */
+ public void testCrashLeavesSearchable() throws Exception {
+ path = _TestUtil.getTempDir("testCrashLeavesSearchable");
+
+ indexAndCrashOnCreateOutputSegments2();
+
+ searchForFleas(2);
+
+ }
+
+ /**
+ * index 1 document and commit.
+ * prepare for crashing.
+ * index 1 more document, and upon commit, creation of segments_2 will crash.
+ *
+ * @throws IOException
+ */
+ private void indexAndCrashOnCreateOutputSegments2() throws IOException {
+ Directory realDirectory = null;
+ CrashAfterCreateOutput crashAfterCreateOutput = null;
+ IndexWriter indexWriter = null;
+ try {
+ LockFactory lockFactory = new SimpleFSLockFactory();
+ realDirectory = FSDirectory.open(path, lockFactory);
+ crashAfterCreateOutput = new CrashAfterCreateOutput(realDirectory, lockFactory);
+
+ IndexWriterConfig indexWriterConfig = new IndexWriterConfig(version, analyzer);
+ MergeScheduler mergeScheduler = new SerialMergeScheduler();
+ indexWriterConfig.setMergeScheduler(mergeScheduler);
+ indexWriter = new IndexWriter(crashAfterCreateOutput, indexWriterConfig);
+
+ Document document = getDocument(0);
+ indexWriter.addDocument(document);
+ indexWriter.commit();
+
+ crashAfterCreateOutput.setCrashAfterCreateOutput("segments_2");
+ document = getDocument(1);
+ indexWriter.addDocument(document);
+ try {
+ indexWriter.commit();
+
+ fail("code will not get here");
+ } catch (CrashingException e) {
+ // expected
+ }
+ } finally {
+ try {
+ if (null != indexWriter) {
+ indexWriter.close();
+ }
+ } finally {
+ try {
+ if (null != crashAfterCreateOutput) {
+ crashAfterCreateOutput.close();
+ }
+ } finally {
+ try {
+ if (null != realDirectory) {
+ realDirectory.close();
+ }
+ } finally {
+ realDirectory = null;
+ crashAfterCreateOutput = null;
+ indexWriter = null;
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Attempts to index another 1 document.
+ *
+ * @throws IOException
+ */
+ private void indexAfterRestart() throws IOException {
+ Directory realDirectory = null;
+ IndexWriter indexWriter = null;
+ try {
+ LockFactory lockFactory = new SimpleFSLockFactory();
+ realDirectory = FSDirectory.open(path, lockFactory);
+ IndexWriterConfig indexWriterConfig = new IndexWriterConfig(version, analyzer);
+ MergeScheduler mergeScheduler = new SerialMergeScheduler();
+ indexWriterConfig.setMergeScheduler(mergeScheduler);
+
+ // this line fails because it doesn't know what to do with the created
+ // but empty segments_2 file
+ indexWriter = new IndexWriter(realDirectory, indexWriterConfig);
+
+ // currently the test fails above.
+ // however, to test the fix, the following lines should pass as well.
+ Document document = getDocument(2);
+ indexWriter.addDocument(document);
+ indexWriter.commit();
+ } finally {
+ try {
+ if (null != indexWriter) {
+ indexWriter.close();
+ }
+ } finally {
+ try {
+ if (null != realDirectory) {
+ realDirectory.close();
+ }
+ } finally {
+ realDirectory = null;
+ indexWriter = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Run an example search.
+ *
+ * @throws IOException
+ * @throws ParseException
+ */
+ private void searchForFleas(final int expectedTotalHits) throws IOException, ParseException {
+ Directory realDirectory = null;
+ IndexReader indexReader = null;
+ IndexSearcher indexSearcher = null;
+ try {
+ LockFactory lockFactory = new SimpleFSLockFactory();
+ realDirectory = FSDirectory.open(path, lockFactory);
+ indexReader = IndexReader.open(realDirectory);
+ indexSearcher = new IndexSearcher(indexReader);
+
+ QueryParser queryParser = new QueryParser(version, TEXT_FIELD, analyzer);
+
+ Query query = queryParser.parse("fleas");
+ TopDocs topDocs = indexSearcher.search(query, 10);
+ assertTrue("topDocs was null", null != topDocs);
+ assertTrue("topDocs.totalHits expected "+expectedTotalHits+", got "+topDocs.totalHits, expectedTotalHits == topDocs.totalHits);
+ } finally {
+ try {
+ if (null != indexSearcher) {
+ indexSearcher.close();
+ }
+ } finally {
+ try {
+ if (null != indexReader) {
+ indexReader.close();
+ }
+ } finally {
+ try {
+ if (null != realDirectory) {
+ realDirectory.close();
+ }
+ } finally {
+ realDirectory = null;
+ indexReader = null;
+ indexSearcher = null;
+ }
+ }
+ }
+ }
+ }
+
+ private static final String TEXT_FIELD = "text";
+
+ /**
+ * Gets a document with content "my dog has fleas" and id based on docNumber,
+ * for the purposes of indexing.
+ *
+ * @param docNumber
+ * @return
+ */
+ private Document getDocument(int docNumber) {
+ Document document = new Document();
+
+ Fieldable fieldable = new Field(TEXT_FIELD, "my dog has fleas", Store.NO, Index.ANALYZED);
+ document.add(fieldable);
+ fieldable = new Field("id", "id"+docNumber, Store.YES, Index.NOT_ANALYZED_NO_NORMS);
+ document.add(fieldable);
+
+ return document;
+ }
+
+ /**
+ * The marker RuntimeException that we use in lieu of an actual machine crash.
+ *
+ * @author Ken McCracken
+ */
+ private static class CrashingException extends RuntimeException {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public CrashingException(String msg) {
+ super(msg);
+ }
+
+ }
+
+ /**
+ * This test class provides direct access to "simulating" a crash right after
+ * realDirectory.createOutput(..) has been called on a certain specified name.
+ *
+ * @author Ken McCracken
+ */
+ private static class CrashAfterCreateOutput extends Directory {
+
+ private Directory realDirectory;
+ private String crashAfterCreateOutput;
+
+ public CrashAfterCreateOutput(Directory realDirectory, LockFactory lockFactory) {
+ this.realDirectory = realDirectory;
+ this.lockFactory = lockFactory;
+ }
+
+ public void setCrashAfterCreateOutput(String name) {
+ this.crashAfterCreateOutput = name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() throws IOException {
+ realDirectory.close();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IndexOutput createOutput(String name) throws IOException {
+ IndexOutput indexOutput = realDirectory.createOutput(name);
+ if (null != crashAfterCreateOutput && name.equals(crashAfterCreateOutput)) {
+ // CRASH!
+ indexOutput.close();
+ throw new CrashingException("crashAfterCreateOutput "+crashAfterCreateOutput);
+ }
+ return indexOutput;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void deleteFile(String name) throws IOException {
+ realDirectory.deleteFile(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean fileExists(String name) throws IOException {
+ return realDirectory.fileExists(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long fileLength(String name) throws IOException {
+ return realDirectory.fileLength(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long fileModified(String name) throws IOException {
+ return realDirectory.fileModified(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] listAll() throws IOException {
+ return realDirectory.listAll();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IndexInput openInput(String name) throws IOException {
+ return realDirectory.openInput(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void touchFile(String name) throws IOException {
+ realDirectory.touchFile(name);
+ }
+
+ }
+
+}
Index: src/java/org/apache/lucene/index/IndexFileDeleter.java
===================================================================
--- src/java/org/apache/lucene/index/IndexFileDeleter.java (revision 1211687)
+++ src/java/org/apache/lucene/index/IndexFileDeleter.java (working copy)
@@ -29,6 +29,7 @@
import java.util.Map;
import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.NoSuchDirectoryException;
import org.apache.lucene.util.CollectionUtil;
@@ -194,7 +195,29 @@
sis = null;
} catch (IOException e) {
if (SegmentInfos.generationFromSegmentsFileName(fileName) <= currentGen) {
- throw e;
+ boolean shouldPropogateException = true;
+ IndexInput indexInput = null;
+ try {
+ indexInput = directory.openInput(fileName);
+ if (indexInput.length() == 0L) {
+ // try to delete the file
+ try {
+ indexInput.close();
+ indexInput = null;
+ } finally {
+ directory.deleteFile(fileName);
+ shouldPropogateException = false;
+ }
+ }
+ } finally {
+ if (null != indexInput) {
+ indexInput.close();
+ }
+ }
+ if (shouldPropogateException) {
+ throw e;
+ }
+ sis = null;
} else {
// Most likely we are opening an index that
// has an aborted "future" commit, so suppress