Skip to content

Commit 68fed48

Browse files
committed
Do not swallow exception in ChecksumBlobStoreFormat.writeAtomic() (#27597)
The ChecksumBlobStoreFormat.writeAtomic() method writes a blob using a temporary name and then moves the blob to its final name. The move operation can fail and in this case the temporary blob is deleted. If this delete operation also fails, then the initial exception is lost. This commit ensures that when something goes wrong during the move operation the initial exception is kept and thrown, and if the delete operation also fails then this additional exception is added as a suppressed exception to the initial one.
1 parent 65af4c6 commit 68fed48

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

core/src/main/java/org/elasticsearch/repositories/blobstore/ChecksumBlobStoreFormat.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,11 @@ public void writeAtomic(T obj, BlobContainer blobContainer, String name) throws
138138
blobContainer.move(tempBlobName, blobName);
139139
} catch (IOException ex) {
140140
// Move failed - try cleaning up
141-
blobContainer.deleteBlob(tempBlobName);
141+
try {
142+
blobContainer.deleteBlob(tempBlobName);
143+
} catch (Exception e) {
144+
ex.addSuppressed(e);
145+
}
142146
throw ex;
143147
}
144148
}

core/src/test/java/org/elasticsearch/snapshots/BlobStoreFormatIT.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.elasticsearch.index.translog.BufferedChecksumStreamOutput;
4545
import org.elasticsearch.repositories.blobstore.ChecksumBlobStoreFormat;
4646
import org.elasticsearch.repositories.blobstore.LegacyBlobStoreFormat;
47+
import org.elasticsearch.snapshots.mockstore.BlobContainerWrapper;
4748

4849
import java.io.EOFException;
4950
import java.io.IOException;
@@ -278,6 +279,67 @@ public Void call() throws Exception {
278279
}
279280
}
280281

282+
public void testAtomicWriteFailures() throws Exception {
283+
final String name = randomAlphaOfLength(10);
284+
final BlobObj blobObj = new BlobObj("test");
285+
final ChecksumBlobStoreFormat<BlobObj> checksumFormat = new ChecksumBlobStoreFormat<>(BLOB_CODEC, "%s", BlobObj::fromXContent,
286+
xContentRegistry(), randomBoolean(), randomBoolean() ? XContentType.SMILE : XContentType.JSON);
287+
288+
final BlobStore blobStore = createTestBlobStore();
289+
final BlobContainer blobContainer = blobStore.blobContainer(BlobPath.cleanPath());
290+
291+
{
292+
IOException writeBlobException = expectThrows(IOException.class, () -> {
293+
BlobContainer wrapper = new BlobContainerWrapper(blobContainer) {
294+
@Override
295+
public void writeBlob(String blobName, InputStream inputStream, long blobSize) throws IOException {
296+
throw new IOException("Exception thrown in writeBlob() for " + blobName);
297+
}
298+
};
299+
checksumFormat.writeAtomic(blobObj, wrapper, name);
300+
});
301+
302+
assertEquals("Exception thrown in writeBlob() for pending-" + name, writeBlobException.getMessage());
303+
assertEquals(0, writeBlobException.getSuppressed().length);
304+
}
305+
{
306+
IOException moveException = expectThrows(IOException.class, () -> {
307+
BlobContainer wrapper = new BlobContainerWrapper(blobContainer) {
308+
@Override
309+
public void move(String sourceBlobName, String targetBlobName) throws IOException {
310+
throw new IOException("Exception thrown in move() for " + sourceBlobName);
311+
}
312+
};
313+
checksumFormat.writeAtomic(blobObj, wrapper, name);
314+
});
315+
assertEquals("Exception thrown in move() for pending-" + name, moveException.getMessage());
316+
assertEquals(0, moveException.getSuppressed().length);
317+
}
318+
{
319+
IOException moveThenDeleteException = expectThrows(IOException.class, () -> {
320+
BlobContainer wrapper = new BlobContainerWrapper(blobContainer) {
321+
@Override
322+
public void move(String sourceBlobName, String targetBlobName) throws IOException {
323+
throw new IOException("Exception thrown in move() for " + sourceBlobName);
324+
}
325+
326+
@Override
327+
public void deleteBlob(String blobName) throws IOException {
328+
throw new IOException("Exception thrown in deleteBlob() for " + blobName);
329+
}
330+
};
331+
checksumFormat.writeAtomic(blobObj, wrapper, name);
332+
});
333+
334+
assertEquals("Exception thrown in move() for pending-" + name, moveThenDeleteException.getMessage());
335+
assertEquals(1, moveThenDeleteException.getSuppressed().length);
336+
337+
final Throwable suppressedThrowable = moveThenDeleteException.getSuppressed()[0];
338+
assertTrue(suppressedThrowable instanceof IOException);
339+
assertEquals("Exception thrown in deleteBlob() for pending-" + name, suppressedThrowable.getMessage());
340+
}
341+
}
342+
281343
protected BlobStore createTestBlobStore() throws IOException {
282344
Settings settings = Settings.builder().build();
283345
return new FsBlobStore(settings, randomRepoPath());

0 commit comments

Comments
 (0)