You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ro...@apache.org on 2026/10/22 11:38:06 UTC
(lucene) branch branch_10_6 updated: SegmentInfos: Ensure IOExceptions are not hidden behind CorruptSegmentInfoException (#16788)
This is an automated email from the ASF dual-hosted git repository.
romseygeek pushed a commit to branch branch_10_6
in repository https://gitbox.apache.org/repos/asf/lucene.git
The following commit(s) were added to refs/heads/branch_10_6 by this push:
new 1b61ada0305 SegmentInfos: Ensure IOExceptions are not hidden behind CorruptSegmentInfoException (#16788)
1b61ada0305 is described below
commit 1b61ada030502ba89802cb38197a010c7f1d0617
Author: Alexander Reelsen <al...@spinscale.de>
AuthorDate: Wed Oct 21 17:01:45 2026 +0200
SegmentInfos: Ensure IOExceptions are not hidden behind CorruptSegmentInfoException (#16788)
This is a follow-up for #16476
The previous change hid every IOException behind a CorruptSegmentInfoException.
This could also result in transient errors being treated like a corruption, so we handle
specific issues (EOFException, FileNotFoundException) instead and propagate general
IOExceptions to the caller.
---
lucene/CHANGES.txt | 5 +-
.../java/org/apache/lucene/index/SegmentInfos.java | 12 ++---
.../org/apache/lucene/index/TestCheckIndex.java | 61 ++++++++++++++++++++++
.../org/apache/lucene/index/TestTransactions.java | 5 +-
4 files changed, 71 insertions(+), 12 deletions(-)
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 36fef81cf2d..0366a718138 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -109,9 +109,10 @@ Improvements
* GITHUB#16091: Replace deprecated IndexSearcher#search(Query, Collector) usage with CollectorManager
in JoinUtil#createJoinQuery for the GlobalOrdinalsCollector case. (Luca Cavanna)
-* GITHUB#7820: CheckIndex now reports which segment's .si file could not be read, rather than only that
+* GITHUB#17820: CheckIndex now reports which segment's .si file could not be read, rather than only that
a commit point was unreadable. SegmentInfos throws CorruptSegmentInfoException, carrying the segment
- name and the underlying failure. (Gokul Manoj, Serhiy Bzhezytskyy)
+ name and the underlying failure, when a .si file is missing or corrupt; other I/O failures, such as
+ transient ones, propagate unchanged. (Gokul Manoj, Serhiy Bzhezytskyy, Alex Reelsen)
* GITHUB#16292: Introduce TermGroupFacetCollectorManager to enable concurrent search with grouped
faceting, removing usages of the deprecated IndexSearcher#search(Query, Collector).
diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java b/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
index b53c8c6d639..d2b69360406 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
@@ -45,7 +45,6 @@ import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.StringHelper;
-import org.apache.lucene.util.ThreadInterruptedException;
import org.apache.lucene.util.Version;
/**
@@ -409,11 +408,12 @@ public final class SegmentInfos implements Cloneable, Iterable<SegmentCommitInfo
final SegmentInfo info;
try {
info = codec.segmentInfoFormat().read(directory, segName, segmentID, IOContext.READONCE);
- } catch (ThreadInterruptedException e) {
- throw e;
- } catch (Exception | AssertionError e) {
- // Corruption in a .si file can surface as almost anything the codec's reader happens to do
- // with the bad bytes, so catch broadly, but keep the root cause: it is what names the file.
+ } catch (CorruptIndexException
+ | EOFException
+ | NoSuchFileException
+ | FileNotFoundException e) {
+ // The .si is missing, truncated or failed its checksum. Anything else, e.g. a transient I/O
+ // failure, is no evidence of corruption and must propagate as-is.
throw new CorruptSegmentInfoException(
segName,
"segment info file: " + segName + ".si cannot be read - it may be missing or corrupt",
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestCheckIndex.java b/lucene/core/src/test/org/apache/lucene/index/TestCheckIndex.java
index 7092e0f3ed0..55c79dc766e 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestCheckIndex.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestCheckIndex.java
@@ -37,6 +37,8 @@ import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FilterDirectory;
+import org.apache.lucene.store.FilterIndexInput;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;
@@ -366,6 +368,65 @@ public class TestCheckIndex extends BaseTestCheckIndex {
}
}
+ public void testSegmentInfoIOExceptionDoesNotThrowCorruptionException() throws Exception {
+ for (String failure : List.of("fail-open", "fail-read")) {
+ try (Directory dir = newDirectory()) {
+ IndexWriterConfig iwc = new IndexWriterConfig().setMergePolicy(NoMergePolicy.INSTANCE);
+ try (IndexWriter iw = new IndexWriter(dir, iwc)) {
+ for (int seg = 0; seg < 2; seg++) {
+ Document doc = new Document();
+ doc.add(new StringField("id", "d" + seg, Field.Store.NO));
+ iw.addDocument(doc);
+ iw.commit();
+ }
+ }
+
+ Directory failingDir =
+ new FilterDirectory(dir) {
+ @Override
+ public IndexInput openInput(String name, IOContext context) throws IOException {
+ if (name.equals("_1.si") == false) {
+ return super.openInput(name, context);
+ }
+ if (failure.equals("fail-open")) {
+ throw new IOException("simulated I/O failure opening " + name);
+ }
+ IndexInput in = super.openInput(name, context);
+ return new FilterIndexInput("failing(" + in + ")", in) {
+ @Override
+ public byte readByte() throws IOException {
+ throw new IOException("simulated I/O failure reading " + name);
+ }
+
+ @Override
+ public void readBytes(byte[] b, int offset, int len) throws IOException {
+ throw new IOException("simulated I/O failure reading " + name);
+ }
+ };
+ }
+ };
+
+ IOException e =
+ expectThrows(
+ IOException.class,
+ () ->
+ SegmentInfos.readCommit(
+ failingDir, SegmentInfos.getLastCommitSegmentsFileName(failingDir), 0));
+ assertFalse(failure + ": " + e, e instanceof CorruptIndexException);
+ assertTrue(failure + ": " + e, e.getMessage().startsWith("simulated I/O failure"));
+
+ // ... and CheckIndex does not blame the segment for it
+ try (CheckIndex checker = new CheckIndex(failingDir)) {
+ CheckIndex.Status status = checker.checkIndex();
+
+ assertFalse(failure, status.clean);
+ assertTrue(failure, status.missingSegments);
+ assertNull(failure, status.brokenSegmentName);
+ }
+ }
+ }
+ }
+
/** Rewrites {@code name} keeping only its first 70% of bytes. */
private static void truncate(Directory dir, String name) throws IOException {
byte[] bytes;
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestTransactions.java b/lucene/core/src/test/org/apache/lucene/index/TestTransactions.java
index c6cfffd0b6b..22c79b22d9e 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestTransactions.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestTransactions.java
@@ -218,10 +218,7 @@ public class TestTransactions extends LuceneTestCase {
} catch (Exception e) {
// can be rethrown as RuntimeException if it happens during a close listener
if (!e.getMessage().contains("on purpose")) {
- // Caught "on-purpose" IOException can be rethrown as CorruptSegmentInfoException
- if (e instanceof CorruptSegmentInfoException == false) {
- throw e;
- }
+ throw e;
}
// release resources
IOUtils.closeWhileHandlingException(r1, r2);