Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public class SnapshotManager implements Serializable {

public static final int EARLIEST_SNAPSHOT_DEFAULT_RETRY_NUM = 300;

private static final int SNAPSHOT_EXISTS_MAX_ATTEMPTS = 3;
private static final long SNAPSHOT_EXISTS_RETRY_INTERVAL_MILLIS = 1_000L;

private final FileIO fileIO;
private final Path tablePath;
private final String branch;
Expand Down Expand Up @@ -147,13 +150,44 @@ public Snapshot tryGetSnapshot(long snapshotId) throws FileNotFoundException {

public boolean snapshotExists(long snapshotId) {
Path path = snapshotPath(snapshotId);
try {
return fileIO.exists(path);
} catch (IOException e) {
throw new RuntimeException(
"Failed to determine if snapshot #" + snapshotId + " exists in path " + path,
e);
IOException failure = null;
for (int attempt = 1; attempt <= SNAPSHOT_EXISTS_MAX_ATTEMPTS; attempt++) {
try {
return fileIO.exists(path);
} catch (IOException e) {
failure = e;
if (attempt == SNAPSHOT_EXISTS_MAX_ATTEMPTS) {
break;
}
LOG.warn(
"Failed to check whether snapshot #{} exists at {} (attempt {}/{}). Retrying.",
snapshotId,
path,
attempt,
SNAPSHOT_EXISTS_MAX_ATTEMPTS,
e);
try {
Thread.sleep(SNAPSHOT_EXISTS_RETRY_INTERVAL_MILLIS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(
"Interrupted while checking whether snapshot #"
+ snapshotId
+ " exists at "
+ path,
ie);
}
}
}
throw new RuntimeException(
"Failed to check whether snapshot #"
+ snapshotId
+ " exists at "
+ path
+ " after "
+ SNAPSHOT_EXISTS_MAX_ATTEMPTS
+ " attempts.",
failure);
}

public void deleteSnapshot(long snapshotId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,32 @@ public void testSnapshotsWithIdSkipsExpiredSnapshot() throws Exception {
.isFalse();
}

@Test
public void testSnapshotExistsRetriesAfterIOException() throws IOException {
FileIO fileIO = Mockito.mock(FileIO.class);
Mockito.when(fileIO.exists(Mockito.any(Path.class)))
.thenThrow(new IOException("Temporary failure"))
.thenReturn(true);
SnapshotManager snapshotManager = newSnapshotManager(fileIO, new Path(tempDir.toString()));

assertThat(snapshotManager.snapshotExists(2)).isTrue();
Mockito.verify(fileIO, Mockito.times(2)).exists(Mockito.any(Path.class));
}

@Test
public void testSnapshotExistsFailsAfterMaxAttempts() throws IOException {
FileIO fileIO = Mockito.mock(FileIO.class);
Mockito.when(fileIO.exists(Mockito.any(Path.class)))
.thenThrow(new IOException("Persistent failure"));
SnapshotManager snapshotManager = newSnapshotManager(fileIO, new Path(tempDir.toString()));

assertThatThrownBy(() -> snapshotManager.snapshotExists(2))
.hasMessageContaining("Failed to check whether snapshot #2 exists")
.hasMessageContaining("after 3 attempts")
.hasRootCauseMessage("Persistent failure");
Mockito.verify(fileIO, Mockito.times(3)).exists(Mockito.any(Path.class));
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testEarliestSnapshot(boolean isRaceCondition) throws IOException {
Expand Down
Loading