Skip to content

Commit cc2b3ec

Browse files
buchgrcopybara-github
authored andcommitted
Only request grpc write when not complete
If a queryWriteStatus yields a committedSize which leaves no content remaining to be uploaded, immediately succeed a blob upload. This can easily occur if a competing blob write completes asynchronously between abnormal write termination and a query. Closes #10284. PiperOrigin-RevId: 281944912
1 parent a6cd408 commit cc2b3ec

3 files changed

Lines changed: 66 additions & 2 deletions

File tree

src/main/java/com/google/devtools/build/lib/remote/ByteStreamUploader.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,12 @@ ListenableFuture<Void> start() {
353353
AtomicLong committedOffset = new AtomicLong(0);
354354
return Futures.transformAsync(
355355
retrier.executeAsync(
356-
() -> ctx.call(() -> callAndQueryOnFailure(committedOffset, progressiveBackoff)),
356+
() -> {
357+
if (committedOffset.get() < chunker.getSize()) {
358+
return ctx.call(() -> callAndQueryOnFailure(committedOffset, progressiveBackoff));
359+
}
360+
return Futures.immediateFuture(null);
361+
},
357362
progressiveBackoff),
358363
(result) -> {
359364
long committedSize = committedOffset.get();

src/main/java/com/google/devtools/build/lib/remote/Chunker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public Chunk next() throws IOException {
218218
return new Chunk(blob, offsetBefore);
219219
}
220220

221-
private long bytesLeft() {
221+
public long bytesLeft() {
222222
return getSize() - getOffset();
223223
}
224224

src/test/java/com/google/devtools/build/lib/remote/ByteStreamUploaderTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,65 @@ public void queryWriteStatus(
333333
withEmptyMetadata.detach(prevContext);
334334
}
335335

336+
@Test
337+
public void concurrentlyCompletedUploadIsNotRetried() throws Exception {
338+
// Test that after an upload has failed and the QueryWriteStatus call returns
339+
// that the upload has completed that we'll not retry the upload.
340+
Context prevContext = withEmptyMetadata.attach();
341+
RemoteRetrier retrier =
342+
TestUtils.newRemoteRetrier(() -> new FixedBackoff(1, 0), (e) -> true, retryService);
343+
ByteStreamUploader uploader =
344+
new ByteStreamUploader(
345+
INSTANCE_NAME, new ReferenceCountedChannel(channel), null, 1, retrier);
346+
347+
byte[] blob = new byte[CHUNK_SIZE * 2 + 1];
348+
new Random().nextBytes(blob);
349+
350+
Chunker chunker = Chunker.builder().setInput(blob).setChunkSize(CHUNK_SIZE).build();
351+
HashCode hash = HashCode.fromString(DIGEST_UTIL.compute(blob).getHash());
352+
353+
AtomicInteger numWriteCalls = new AtomicInteger(0);
354+
355+
serviceRegistry.addService(
356+
new ByteStreamImplBase() {
357+
@Override
358+
public StreamObserver<WriteRequest> write(StreamObserver<WriteResponse> streamObserver) {
359+
numWriteCalls.getAndIncrement();
360+
streamObserver.onError(Status.DEADLINE_EXCEEDED.asException());
361+
return new StreamObserver<WriteRequest>() {
362+
@Override
363+
public void onNext(WriteRequest writeRequest) {}
364+
365+
@Override
366+
public void onError(Throwable throwable) {}
367+
368+
@Override
369+
public void onCompleted() {}
370+
};
371+
}
372+
373+
@Override
374+
public void queryWriteStatus(
375+
QueryWriteStatusRequest request, StreamObserver<QueryWriteStatusResponse> response) {
376+
response.onNext(
377+
QueryWriteStatusResponse.newBuilder()
378+
.setCommittedSize(blob.length)
379+
.setComplete(true)
380+
.build());
381+
response.onCompleted();
382+
}
383+
});
384+
385+
uploader.uploadBlob(hash, chunker, true);
386+
387+
// This test should not have triggered any retries.
388+
assertThat(numWriteCalls.get()).isEqualTo(1);
389+
390+
blockUntilInternalStateConsistent(uploader);
391+
392+
withEmptyMetadata.detach(prevContext);
393+
}
394+
336395
@Test
337396
public void unimplementedQueryShouldRestartUpload() throws Exception {
338397
Context prevContext = withEmptyMetadata.attach();

0 commit comments

Comments
 (0)