mirror of https://github.com/apache/lucene.git
IndexWriter: Treat java.lang.Error as tragedy (#13277)
Background: Historically IndexWriter treated OutOfMemoryError special, for defensive reasons. It was expanded to VirtualMachineError, to try to play it safe in similar disastrous circumstances. We should treat any Error as a tragedy, as it isn't an Exception, and it isn't something a "reasonable" application should catch. IndexWriter should be reasonable. See #7049 for some of the reasoning. We can't pretend this will detect any possible scenario that might cause harm, e.g. a jvm bug might simply miscompile some code and cause silent corruption. But we should try harder by playing by the rules. Closes #13275 Closes #7049
This commit is contained in:
parent
fb12e09ab5
commit
9af3ef8952
|
@ -183,6 +183,8 @@ Changes in Runtime Behavior
|
||||||
* GITHUB#13293: ConcurrentMergeScheduler now allows up to 50% of the threads of the host to be used
|
* GITHUB#13293: ConcurrentMergeScheduler now allows up to 50% of the threads of the host to be used
|
||||||
for merging. (Adrien Grand)
|
for merging. (Adrien Grand)
|
||||||
|
|
||||||
|
* GITHUB#13277: IndexWriter treats any java.lang.Error as tragic. (Robert Muir)
|
||||||
|
|
||||||
Changes in Backwards Compatibility Policy
|
Changes in Backwards Compatibility Policy
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -153,9 +153,9 @@ import org.apache.lucene.util.Version;
|
||||||
* and it decides when and how to run the merges. The default is {@link ConcurrentMergeScheduler}.
|
* and it decides when and how to run the merges. The default is {@link ConcurrentMergeScheduler}.
|
||||||
* <a id="OOME"></a>
|
* <a id="OOME"></a>
|
||||||
*
|
*
|
||||||
* <p><b>NOTE</b>: if you hit a VirtualMachineError, or disaster strikes during a checkpoint then
|
* <p><b>NOTE</b>: if you hit an Error, or disaster strikes during a checkpoint then IndexWriter
|
||||||
* IndexWriter will close itself. This is a defensive measure in case any internal state (buffered
|
* will close itself. This is a defensive measure in case any internal state (buffered documents,
|
||||||
* documents, deletions, reference counts) were corrupted. Any subsequent calls will throw an
|
* deletions, reference counts) were corrupted. Any subsequent calls will throw an
|
||||||
* AlreadyClosedException. <a id="thread-safety"></a>
|
* AlreadyClosedException. <a id="thread-safety"></a>
|
||||||
*
|
*
|
||||||
* <p><b>NOTE</b>: {@link IndexWriter} instances are completely thread safe, meaning multiple
|
* <p><b>NOTE</b>: {@link IndexWriter} instances are completely thread safe, meaning multiple
|
||||||
|
@ -704,7 +704,7 @@ public class IndexWriter
|
||||||
infoStream.message("IW", "getReader took " + (System.currentTimeMillis() - tStart) + " ms");
|
infoStream.message("IW", "getReader took " + (System.currentTimeMillis() - tStart) + " ms");
|
||||||
}
|
}
|
||||||
success2 = true;
|
success2 = true;
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "getReader");
|
tragicEvent(tragedy, "getReader");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -1549,7 +1549,7 @@ public class IndexWriter
|
||||||
final long seqNo = maybeProcessEvents(docWriter.updateDocuments(docs, delNode));
|
final long seqNo = maybeProcessEvents(docWriter.updateDocuments(docs, delNode));
|
||||||
success = true;
|
success = true;
|
||||||
return seqNo;
|
return seqNo;
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "updateDocuments");
|
tragicEvent(tragedy, "updateDocuments");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -1786,7 +1786,7 @@ public class IndexWriter
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
try {
|
try {
|
||||||
return maybeProcessEvents(docWriter.deleteTerms(terms));
|
return maybeProcessEvents(docWriter.deleteTerms(terms));
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "deleteDocuments(Term..)");
|
tragicEvent(tragedy, "deleteDocuments(Term..)");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
@ -1813,7 +1813,7 @@ public class IndexWriter
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return maybeProcessEvents(docWriter.deleteQueries(queries));
|
return maybeProcessEvents(docWriter.deleteQueries(queries));
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "deleteDocuments(Query..)");
|
tragicEvent(tragedy, "deleteDocuments(Query..)");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
@ -1892,7 +1892,7 @@ public class IndexWriter
|
||||||
try {
|
try {
|
||||||
return maybeProcessEvents(
|
return maybeProcessEvents(
|
||||||
docWriter.updateDocValues(new NumericDocValuesUpdate(term, field, value)));
|
docWriter.updateDocValues(new NumericDocValuesUpdate(term, field, value)));
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "updateNumericDocValue");
|
tragicEvent(tragedy, "updateNumericDocValue");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
@ -1922,7 +1922,7 @@ public class IndexWriter
|
||||||
try {
|
try {
|
||||||
return maybeProcessEvents(
|
return maybeProcessEvents(
|
||||||
docWriter.updateDocValues(new BinaryDocValuesUpdate(term, field, value)));
|
docWriter.updateDocValues(new BinaryDocValuesUpdate(term, field, value)));
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "updateBinaryDocValue");
|
tragicEvent(tragedy, "updateBinaryDocValue");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
@ -1944,7 +1944,7 @@ public class IndexWriter
|
||||||
DocValuesUpdate[] dvUpdates = buildDocValuesUpdate(term, updates);
|
DocValuesUpdate[] dvUpdates = buildDocValuesUpdate(term, updates);
|
||||||
try {
|
try {
|
||||||
return maybeProcessEvents(docWriter.updateDocValues(dvUpdates));
|
return maybeProcessEvents(docWriter.updateDocValues(dvUpdates));
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "updateDocValues");
|
tragicEvent(tragedy, "updateDocValues");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
@ -2577,7 +2577,7 @@ public class IndexWriter
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throwable.addSuppressed(t);
|
throwable.addSuppressed(t);
|
||||||
} finally {
|
} finally {
|
||||||
if (throwable instanceof VirtualMachineError) {
|
if (throwable instanceof Error) {
|
||||||
try {
|
try {
|
||||||
tragicEvent(throwable, "rollbackInternal");
|
tragicEvent(throwable, "rollbackInternal");
|
||||||
} catch (Throwable t1) {
|
} catch (Throwable t1) {
|
||||||
|
@ -2678,7 +2678,7 @@ public class IndexWriter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "deleteAll");
|
tragicEvent(tragedy, "deleteAll");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
@ -3098,7 +3098,7 @@ public class IndexWriter
|
||||||
|
|
||||||
successTop = true;
|
successTop = true;
|
||||||
|
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "addIndexes(Directory...)");
|
tragicEvent(tragedy, "addIndexes(Directory...)");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -3270,7 +3270,7 @@ public class IndexWriter
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"failed to successfully merge all provided readers in addIndexes(CodecReader...)");
|
"failed to successfully merge all provided readers in addIndexes(CodecReader...)");
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "addIndexes(CodecReader...)");
|
tragicEvent(tragedy, "addIndexes(CodecReader...)");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
@ -3623,7 +3623,7 @@ public class IndexWriter
|
||||||
return true; // we wrote a segment
|
return true; // we wrote a segment
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "flushNextBuffer");
|
tragicEvent(tragedy, "flushNextBuffer");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -3739,7 +3739,7 @@ public class IndexWriter
|
||||||
doAfterFlush();
|
doAfterFlush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "prepareCommit");
|
tragicEvent(tragedy, "prepareCommit");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -4299,7 +4299,7 @@ public class IndexWriter
|
||||||
success = true;
|
success = true;
|
||||||
return anyChanges;
|
return anyChanges;
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "doFlush");
|
tragicEvent(tragedy, "doFlush");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -5693,7 +5693,7 @@ public class IndexWriter
|
||||||
segmentInfos.updateGeneration(toSync);
|
segmentInfos.updateGeneration(toSync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError tragedy) {
|
} catch (Error tragedy) {
|
||||||
tragicEvent(tragedy, "startCommit");
|
tragicEvent(tragedy, "startCommit");
|
||||||
throw tragedy;
|
throw tragedy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,21 +105,20 @@ public final class IOUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes all given <code>Closeable</code>s, suppressing all thrown non {@link
|
* Closes all given <code>Closeable</code>s, suppressing all thrown non {@link Error} exceptions.
|
||||||
* VirtualMachineError} exceptions. Even if a {@link VirtualMachineError} is thrown all given
|
* Even if a {@link Error} is thrown all given closeable are closed.
|
||||||
* closeable are closed.
|
|
||||||
*
|
*
|
||||||
* @see #closeWhileHandlingException(Closeable...)
|
* @see #closeWhileHandlingException(Closeable...)
|
||||||
*/
|
*/
|
||||||
public static void closeWhileHandlingException(Iterable<? extends Closeable> objects) {
|
public static void closeWhileHandlingException(Iterable<? extends Closeable> objects) {
|
||||||
VirtualMachineError firstError = null;
|
Error firstError = null;
|
||||||
Throwable firstThrowable = null;
|
Throwable firstThrowable = null;
|
||||||
for (Closeable object : objects) {
|
for (Closeable object : objects) {
|
||||||
try {
|
try {
|
||||||
if (object != null) {
|
if (object != null) {
|
||||||
object.close();
|
object.close();
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError e) {
|
} catch (Error e) {
|
||||||
firstError = useOrSuppress(firstError, e);
|
firstError = useOrSuppress(firstError, e);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
firstThrowable = useOrSuppress(firstThrowable, t);
|
firstThrowable = useOrSuppress(firstThrowable, t);
|
||||||
|
@ -128,7 +127,7 @@ public final class IOUtils {
|
||||||
if (firstError != null) {
|
if (firstError != null) {
|
||||||
// we ensure that we bubble up any errors. We can't recover from these but need to make sure
|
// we ensure that we bubble up any errors. We can't recover from these but need to make sure
|
||||||
// they are
|
// they are
|
||||||
// bubbled up. if a non-VMError is thrown we also add the suppressed exceptions to it.
|
// bubbled up. if a non-Error is thrown we also add the suppressed exceptions to it.
|
||||||
if (firstThrowable != null) {
|
if (firstThrowable != null) {
|
||||||
firstError.addSuppressed(firstThrowable);
|
firstError.addSuppressed(firstThrowable);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.lucene.index;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOError;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -53,7 +54,7 @@ import org.apache.lucene.util.IOUtils;
|
||||||
* index corruption is ever created.
|
* index corruption is ever created.
|
||||||
*/
|
*/
|
||||||
@SuppressCodecs("SimpleText")
|
@SuppressCodecs("SimpleText")
|
||||||
public class TestIndexWriterOnVMError extends LuceneTestCase {
|
public class TestIndexWriterOnError extends LuceneTestCase {
|
||||||
|
|
||||||
// just one thread, serial merge policy, hopefully debuggable
|
// just one thread, serial merge policy, hopefully debuggable
|
||||||
private void doTest(MockDirectoryWrapper.Failure failOn) throws Exception {
|
private void doTest(MockDirectoryWrapper.Failure failOn) throws Exception {
|
||||||
|
@ -151,7 +152,7 @@ public class TestIndexWriterOnVMError extends LuceneTestCase {
|
||||||
"dv2",
|
"dv2",
|
||||||
new BytesRef(Integer.toString(i + 1)));
|
new BytesRef(Integer.toString(i + 1)));
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError | AlreadyClosedException disaster) {
|
} catch (Error | AlreadyClosedException disaster) {
|
||||||
getTragedy(disaster, iw, exceptionStream);
|
getTragedy(disaster, iw, exceptionStream);
|
||||||
continue STARTOVER;
|
continue STARTOVER;
|
||||||
}
|
}
|
||||||
|
@ -174,7 +175,7 @@ public class TestIndexWriterOnVMError extends LuceneTestCase {
|
||||||
iw.deleteDocuments(
|
iw.deleteDocuments(
|
||||||
new Term("id", Integer.toString(i)), new Term("id", Integer.toString(-i)));
|
new Term("id", Integer.toString(i)), new Term("id", Integer.toString(-i)));
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError | AlreadyClosedException disaster) {
|
} catch (Error | AlreadyClosedException disaster) {
|
||||||
getTragedy(disaster, iw, exceptionStream);
|
getTragedy(disaster, iw, exceptionStream);
|
||||||
continue STARTOVER;
|
continue STARTOVER;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +198,7 @@ public class TestIndexWriterOnVMError extends LuceneTestCase {
|
||||||
if (DirectoryReader.indexExists(dir)) {
|
if (DirectoryReader.indexExists(dir)) {
|
||||||
TestUtil.checkIndex(dir);
|
TestUtil.checkIndex(dir);
|
||||||
}
|
}
|
||||||
} catch (VirtualMachineError | AlreadyClosedException disaster) {
|
} catch (Error | AlreadyClosedException disaster) {
|
||||||
getTragedy(disaster, iw, exceptionStream);
|
getTragedy(disaster, iw, exceptionStream);
|
||||||
continue STARTOVER;
|
continue STARTOVER;
|
||||||
}
|
}
|
||||||
|
@ -206,7 +207,7 @@ public class TestIndexWriterOnVMError extends LuceneTestCase {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
iw.close();
|
iw.close();
|
||||||
} catch (VirtualMachineError | AlreadyClosedException disaster) {
|
} catch (Error | AlreadyClosedException disaster) {
|
||||||
getTragedy(disaster, iw, exceptionStream);
|
getTragedy(disaster, iw, exceptionStream);
|
||||||
continue STARTOVER;
|
continue STARTOVER;
|
||||||
}
|
}
|
||||||
|
@ -225,15 +226,13 @@ public class TestIndexWriterOnVMError extends LuceneTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private VirtualMachineError getTragedy(Throwable disaster, IndexWriter writer, PrintStream log) {
|
private Error getTragedy(Throwable disaster, IndexWriter writer, PrintStream log) {
|
||||||
Throwable e = disaster;
|
Throwable e = disaster;
|
||||||
if (e instanceof AlreadyClosedException) {
|
if (e instanceof AlreadyClosedException) {
|
||||||
e = e.getCause();
|
e = e.getCause();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e instanceof VirtualMachineError
|
if (e instanceof Error && e.getMessage() != null && e.getMessage().contains("Fake")) {
|
||||||
&& e.getMessage() != null
|
|
||||||
&& e.getMessage().startsWith("Fake")) {
|
|
||||||
log.println("\nTEST: got expected fake exc:" + e.getMessage());
|
log.println("\nTEST: got expected fake exc:" + e.getMessage());
|
||||||
e.printStackTrace(log);
|
e.printStackTrace(log);
|
||||||
// TODO: remove rollback here, and add this assert to ensure "full OOM protection" anywhere IW
|
// TODO: remove rollback here, and add this assert to ensure "full OOM protection" anywhere IW
|
||||||
|
@ -244,7 +243,7 @@ public class TestIndexWriterOnVMError extends LuceneTestCase {
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
t.printStackTrace(log);
|
t.printStackTrace(log);
|
||||||
}
|
}
|
||||||
return (VirtualMachineError) e;
|
return (Error) e;
|
||||||
} else {
|
} else {
|
||||||
Rethrow.rethrow(disaster);
|
Rethrow.rethrow(disaster);
|
||||||
return null; // dead
|
return null; // dead
|
||||||
|
@ -281,6 +280,36 @@ public class TestIndexWriterOnVMError extends LuceneTestCase {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testLinkageError() throws Exception {
|
||||||
|
final Random r = new Random(random().nextLong());
|
||||||
|
doTest(
|
||||||
|
new Failure() {
|
||||||
|
@Override
|
||||||
|
public void eval(MockDirectoryWrapper dir) throws IOException {
|
||||||
|
if (r.nextInt(3000) == 0) {
|
||||||
|
if (callStackContains(IndexWriter.class)) {
|
||||||
|
throw new LinkageError("Fake LinkageError");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIOError() throws Exception {
|
||||||
|
final Random r = new Random(random().nextLong());
|
||||||
|
doTest(
|
||||||
|
new Failure() {
|
||||||
|
@Override
|
||||||
|
public void eval(MockDirectoryWrapper dir) throws IOException {
|
||||||
|
if (r.nextInt(3000) == 0) {
|
||||||
|
if (callStackContains(IndexWriter.class)) {
|
||||||
|
throw new IOError(new RuntimeException("Fake IOError"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Nightly
|
@Nightly
|
||||||
public void testCheckpoint() throws Exception {
|
public void testCheckpoint() throws Exception {
|
||||||
final Random r = new Random(random().nextLong());
|
final Random r = new Random(random().nextLong());
|
Loading…
Reference in New Issue