Skip to content

Commit 17ea70d

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 5fc9451 commit 17ea70d

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
@@ -38,6 +38,7 @@
3838
import org.elasticsearch.common.xcontent.XContentType;
3939
import org.elasticsearch.index.translog.BufferedChecksumStreamOutput;
4040
import org.elasticsearch.repositories.blobstore.ChecksumBlobStoreFormat;
41+
import org.elasticsearch.snapshots.mockstore.BlobContainerWrapper;
4142

4243
import java.io.EOFException;
4344
import java.io.IOException;
@@ -210,6 +211,67 @@ public Void call() throws Exception {
210211
}
211212
}
212213

214+
public void testAtomicWriteFailures() throws Exception {
215+
final String name = randomAlphaOfLength(10);
216+
final BlobObj blobObj = new BlobObj("test");
217+
final ChecksumBlobStoreFormat<BlobObj> checksumFormat = new ChecksumBlobStoreFormat<>(BLOB_CODEC, "%s", BlobObj::fromXContent,
218+
xContentRegistry(), randomBoolean(), randomBoolean() ? XContentType.SMILE : XContentType.JSON);
219+
220+
final BlobStore blobStore = createTestBlobStore();
221+
final BlobContainer blobContainer = blobStore.blobContainer(BlobPath.cleanPath());
222+
223+
{
224+
IOException writeBlobException = expectThrows(IOException.class, () -> {
225+
BlobContainer wrapper = new BlobContainerWrapper(blobContainer) {
226+
@Override
227+
public void writeBlob(String blobName, InputStream inputStream, long blobSize) throws IOException {
228+
throw new IOException("Exception thrown in writeBlob() for " + blobName);
229+
}
230+
};
231+
checksumFormat.writeAtomic(blobObj, wrapper, name);
232+
});
233+
234+
assertEquals("Exception thrown in writeBlob() for pending-" + name, writeBlobException.getMessage());
235+
assertEquals(0, writeBlobException.getSuppressed().length);
236+
}
237+
{
238+
IOException moveException = expectThrows(IOException.class, () -> {
239+
BlobContainer wrapper = new BlobContainerWrapper(blobContainer) {
240+
@Override
241+
public void move(String sourceBlobName, String targetBlobName) throws IOException {
242+
throw new IOException("Exception thrown in move() for " + sourceBlobName);
243+
}
244+
};
245+
checksumFormat.writeAtomic(blobObj, wrapper, name);
246+
});
247+
assertEquals("Exception thrown in move() for pending-" + name, moveException.getMessage());
248+
assertEquals(0, moveException.getSuppressed().length);
249+
}
250+
{
251+
IOException moveThenDeleteException = expectThrows(IOException.class, () -> {
252+
BlobContainer wrapper = new BlobContainerWrapper(blobContainer) {
253+
@Override
254+
public void move(String sourceBlobName, String targetBlobName) throws IOException {
255+
throw new IOException("Exception thrown in move() for " + sourceBlobName);
256+
}
257+
258+
@Override
259+
public void deleteBlob(String blobName) throws IOException {
260+
throw new IOException("Exception thrown in deleteBlob() for " + blobName);
261+
}
262+
};
263+
checksumFormat.writeAtomic(blobObj, wrapper, name);
264+
});
265+
266+
assertEquals("Exception thrown in move() for pending-" + name, moveThenDeleteException.getMessage());
267+
assertEquals(1, moveThenDeleteException.getSuppressed().length);
268+
269+
final Throwable suppressedThrowable = moveThenDeleteException.getSuppressed()[0];
270+
assertTrue(suppressedThrowable instanceof IOException);
271+
assertEquals("Exception thrown in deleteBlob() for pending-" + name, suppressedThrowable.getMessage());
272+
}
273+
}
274+
213275
protected BlobStore createTestBlobStore() throws IOException {
214276
Settings settings = Settings.builder().build();
215277
return new FsBlobStore(settings, randomRepoPath());

0 commit comments

Comments
 (0)