LUCENE-6835, LUCENE-6684: move the 'suppress NSFE on windows' hack down lower, out of IFD into FSDir; also fix IFD to remove segments files before others

This commit is contained in:
Mike McCandless 2016-02-07 13:21:39 -05:00
parent e0ba1e5bff
commit f8bd22e58c
3 changed files with 100 additions and 18 deletions

View File

@ -696,24 +696,23 @@ final class IndexFileDeleter implements Closeable {
ensureOpen(); ensureOpen();
if (infoStream.isEnabled("IFD")) { if (infoStream.isEnabled("IFD")) {
infoStream.message("IFD", "delete \"" + names + "\""); infoStream.message("IFD", "delete " + names + "");
}
// We make two passes, first deleting any segments_N files, second deleting the rest. We do this so that if we throw exc or JVM
// crashes during deletions, even when not on Windows, we don't leave the index in an "apparently corrupt" state:
for(String name : names) {
if (name.startsWith(IndexFileNames.SEGMENTS) == false) {
continue;
}
directory.deleteFile(name);
} }
for(String name : names) { for(String name : names) {
try { if (name.startsWith(IndexFileNames.SEGMENTS) == true) {
directory.deleteFile(name); continue;
} catch (NoSuchFileException | FileNotFoundException e) {
// IndexWriter should only ask us to delete files it knows it wrote, so if we hit this, something is wrong!
if (Constants.WINDOWS) {
// TODO: can we remove this OS-specific hacky logic? If windows deleteFile is buggy, we should instead contain this workaround in
// a WindowsFSDirectory ...
// LUCENE-6684: we suppress this assert for Windows, since a file could be in a confusing "pending delete" state, and falsely
// return NSFE/FNFE
} else {
throw e;
}
} }
directory.deleteFile(name);
} }
} }

View File

@ -327,7 +327,7 @@ public abstract class FSDirectory extends BaseDirectory {
if (pendingDeletes.contains(name)) { if (pendingDeletes.contains(name)) {
throw new NoSuchFileException("file \"" + name + "\" is already pending delete"); throw new NoSuchFileException("file \"" + name + "\" is already pending delete");
} }
privateDeleteFile(name); privateDeleteFile(name, false);
maybeDeletePendingFiles(); maybeDeletePendingFiles();
} }
@ -347,7 +347,7 @@ public abstract class FSDirectory extends BaseDirectory {
// Clone the set since we mutate it in privateDeleteFile: // Clone the set since we mutate it in privateDeleteFile:
for(String name : new HashSet<>(pendingDeletes)) { for(String name : new HashSet<>(pendingDeletes)) {
privateDeleteFile(name); privateDeleteFile(name, true);
} }
} }
} }
@ -363,14 +363,21 @@ public abstract class FSDirectory extends BaseDirectory {
} }
} }
private void privateDeleteFile(String name) throws IOException { private void privateDeleteFile(String name, boolean isPendingDelete) throws IOException {
try { try {
Files.delete(directory.resolve(name)); Files.delete(directory.resolve(name));
pendingDeletes.remove(name); pendingDeletes.remove(name);
} catch (NoSuchFileException | FileNotFoundException e) { } catch (NoSuchFileException | FileNotFoundException e) {
// We were asked to delete a non-existent file: // We were asked to delete a non-existent file:
pendingDeletes.remove(name); pendingDeletes.remove(name);
throw e; if (isPendingDelete && Constants.WINDOWS) {
// TODO: can we remove this OS-specific hacky logic? If windows deleteFile is buggy, we should instead contain this workaround in
// a WindowsFSDirectory ...
// LUCENE-6684: we suppress this check for Windows, since a file could be in a confusing "pending delete" state, failing the first
// delete attempt with access denied and then apparently falsely failing here when we try ot delete it again, with NSFE/FNFE
} else {
throw e;
}
} catch (IOException ioe) { } catch (IOException ioe) {
// On windows, a file delete can fail because there's still an open // On windows, a file delete can fail because there's still an open
// file handle against it. We record this in pendingDeletes and // file handle against it. We record this in pendingDeletes and

View File

@ -488,4 +488,80 @@ public class TestIndexFileDeleter extends LuceneTestCase {
w.close(); w.close();
dir.close(); dir.close();
} }
// LUCENE-6835: make sure best-effort to not create an "apparently but not really" corrupt index is working:
public void testExcInDeleteFile() throws Throwable {
int iters = atLeast(10);
for(int iter=0;iter<iters;iter++) {
if (VERBOSE) {
System.out.println("TEST: iter=" + iter);
}
MockDirectoryWrapper dir = newMockDirectory();
final AtomicBoolean doFailExc = new AtomicBoolean();
dir.failOn(new MockDirectoryWrapper.Failure() {
@Override
public void eval(MockDirectoryWrapper dir) throws IOException {
if (doFailExc.get() && random().nextInt(4) == 1) {
Exception e = new Exception();
StackTraceElement stack[] = e.getStackTrace();
for (int i = 0; i < stack.length; i++) {
if (stack[i].getClassName().equals(MockDirectoryWrapper.class.getName()) && stack[i].getMethodName().equals("deleteFile")) {
throw new MockDirectoryWrapper.FakeIOException();
}
}
}
}
});
IndexWriterConfig iwc = newIndexWriterConfig();
iwc.setMergeScheduler(new SerialMergeScheduler());
RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
w.addDocument(new Document());
// makes segments_1
if (VERBOSE) {
System.out.println("TEST: now commit");
}
w.commit();
w.addDocument(new Document());
doFailExc.set(true);
if (VERBOSE) {
System.out.println("TEST: now close");
}
try {
w.close();
if (VERBOSE) {
System.out.println("TEST: no exception (ok)");
}
} catch (RuntimeException re) {
assertTrue(re.getCause() instanceof MockDirectoryWrapper.FakeIOException);
// good
if (VERBOSE) {
System.out.println("TEST: got expected exception:");
re.printStackTrace(System.out);
}
} catch (MockDirectoryWrapper.FakeIOException fioe) {
// good
if (VERBOSE) {
System.out.println("TEST: got expected exception:");
fioe.printStackTrace(System.out);
}
}
doFailExc.set(false);
assertFalse(w.w.isOpen());
for(String name : dir.listAll()) {
if (name.startsWith(IndexFileNames.SEGMENTS)) {
if (VERBOSE) {
System.out.println("TEST: now read " + name);
}
SegmentInfos.readCommit(dir, name);
}
}
dir.close();
}
}
} }