diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/explorer/AbstractSegmentTarExplorerBackend.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/explorer/AbstractSegmentTarExplorerBackend.java index 8b7e76fc13b..96f853a2f3d 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/explorer/AbstractSegmentTarExplorerBackend.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/explorer/AbstractSegmentTarExplorerBackend.java @@ -28,6 +28,7 @@ import org.apache.jackrabbit.oak.segment.SegmentNodeState; import org.apache.jackrabbit.oak.segment.SegmentNodeStateHelper; import org.apache.jackrabbit.oak.segment.SegmentPropertyState; +import org.apache.jackrabbit.oak.segment.file.JournalReadFailure; import org.apache.jackrabbit.oak.segment.file.JournalReader; import org.apache.jackrabbit.oak.segment.file.ReadOnlyFileStore; import org.apache.jackrabbit.oak.segment.spi.persistence.JournalFile; @@ -89,7 +90,7 @@ public List readRevisions() { } finally { journalReader.close(); } - } catch (IOException e) { + } catch (IOException | JournalReadFailure e) { e.printStackTrace(); } finally { try { diff --git a/oak-segment-tar/pom.xml b/oak-segment-tar/pom.xml index 789671ce41b..297503e0257 100644 --- a/oak-segment-tar/pom.xml +++ b/oak-segment-tar/pom.xml @@ -368,6 +368,11 @@ junit test + + com.github.stefanbirkner + system-rules + test + org.apache.sling org.apache.sling.testing.osgi-mock.junit4 diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/JournalReadFailure.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/JournalReadFailure.java new file mode 100644 index 00000000000..258b33c1f6f --- /dev/null +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/JournalReadFailure.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.segment.file; + +import java.io.IOException; + +/** + * Thrown by {@link JournalReader} when an {@link IOException} occurs while + * reading the journal mid-iteration. Using an unchecked exception allows the + * failure to propagate through the {@code Iterator} contract without silently + * masking I/O errors as end-of-data. + */ +public class JournalReadFailure extends RuntimeException { + + public JournalReadFailure(IOException cause) { + super("Failed to read journal file", cause); + } + + @Override + public IOException getCause() { + return (IOException) super.getCause(); + } +} diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/JournalReader.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/JournalReader.java index 8eddf1a642d..4d751bfe09a 100644 --- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/JournalReader.java +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/JournalReader.java @@ -46,8 +46,7 @@ public JournalReader(JournalFile journal) throws IOException { } /** - * @throws IllegalStateException if an {@code IOException} occurs while reading from - * the journal file. + * @throws JournalReadFailure if an {@link IOException} occurs while reading from the journal file. */ @Override protected JournalEntry computeNext() { @@ -76,6 +75,7 @@ protected JournalEntry computeNext() { } } catch (IOException e) { LOG.error("Error reading journal file", e); + throw new JournalReadFailure(e); } return endOfData(); } diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/JournalReaderTest.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/JournalReaderTest.java index 693d760c0a5..c980eac3021 100644 --- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/JournalReaderTest.java +++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/JournalReaderTest.java @@ -22,13 +22,19 @@ import static org.apache.commons.io.FileUtils.write; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.File; import java.io.IOException; import org.apache.jackrabbit.oak.commons.collections.IteratorUtils; import org.apache.jackrabbit.oak.segment.file.tar.LocalJournalFile; +import org.apache.jackrabbit.oak.segment.spi.persistence.JournalFile; +import org.apache.jackrabbit.oak.segment.spi.persistence.JournalFileReader; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -38,6 +44,9 @@ public class JournalReaderTest { @Rule public TemporaryFolder folder = new TemporaryFolder(new File("target")); + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + @Test public void testEmpty() throws IOException { try (JournalReader journalReader = createJournalReader("")) { @@ -142,6 +151,19 @@ public void testIterable() throws IOException { } } + @Test + public void testIOExceptionPropagatesAsJournalReadFailure() throws IOException { + JournalFileReader mockReader = mock(JournalFileReader.class); + when(mockReader.readLine()).thenThrow(new IOException("simulated transient I/O failure")); + + JournalFile mockJournal = mock(JournalFile.class); + when(mockJournal.openJournalReader()).thenReturn(mockReader); + + try (JournalReader journalReader = new JournalReader(mockJournal)) { + assertThrows(JournalReadFailure.class, journalReader::hasNext); + } + } + protected JournalReader createJournalReader(String s) throws IOException { File journalFile = folder.newFile("jrt"); write(journalFile, s);