Skip to content

Commit 64571a4

Browse files
ckolli5benjaminp
andauthored
Ck/cherrypick 15669 (#15788)
* Chunker: Always seek on the uncompressed stream. The `WriteRequest.write_offset` field has bizarre semantics during compressed uploads as documented in the remote API protos: https://github.com/bazelbuild/remote-apis/blob/3b4b6402103539d66fcdd1a4d945f660742665ca/build/bazel/remote/execution/v2/remote_execution.proto#L241-L248 In particular, the write offset of the first `WriteRequest` refers to the offset in the uncompressed source. This change ensures we always seek the uncompressed stream to the correct offset when starting an upload call. The old code could fail to resume compressed uploads under some conditions. The `progressiveCompressedUploadShouldWork` test purported to exercise this situation. The test, however, contained the same logic error as the code under test. Closes #15669. PiperOrigin-RevId: 455083727 Change-Id: Ie22dacf31f15644c7a83f49776e7a633d8bb4bca * Updated chunker.java file. * Update src/test/java/com/google/devtools/build/lib/remote/ByteStreamUploaderTest.java Co-authored-by: Benjamin Peterson <[email protected]> * Update src/test/java/com/google/devtools/build/lib/remote/ByteStreamUploaderTest.java Co-authored-by: Benjamin Peterson <[email protected]> * Update src/test/java/com/google/devtools/build/lib/remote/ByteStreamUploaderTest.java Co-authored-by: Benjamin Peterson <[email protected]> Co-authored-by: Benjamin Peterson <[email protected]> Co-authored-by: Benjamin Peterson <[email protected]>
1 parent 3ea9eb2 commit 64571a4

File tree

2 files changed

+44
-35
lines changed

2 files changed

+44
-35
lines changed

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

+22-8
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,23 @@ public void reset() throws IOException {
142142
}
143143

144144
/**
145-
* Seek to an offset, if necessary resetting or initializing
145+
* Seek to an offset in the source stream.
146146
*
147-
* <p>May close open resources in order to seek to an earlier offset.
147+
* <p>May close and reopen resources in order to seek to an earlier offset.
148+
*
149+
* @param toOffset the offset from beginning of the source stream. If the source stream is
150+
* compressed, it refers to the offset in the uncompressed form to align with `write_offset`
151+
* in REAPI.
148152
*/
149153
public void seek(long toOffset) throws IOException {
150-
if (toOffset < offset) {
154+
// For compressed stream, we need to reinitialize the stream since the offset refers to the
155+
// uncompressed form.
156+
if (initialized && toOffset >= offset && !compressed) {
157+
ByteStreams.skipFully(data, toOffset - offset);
158+
} else {
151159
reset();
160+
initialize(toOffset);
152161
}
153-
maybeInitialize();
154-
ByteStreams.skipFully(data, toOffset - offset);
155-
offset = toOffset;
156162
if (data.finished()) {
157163
close();
158164
}
@@ -245,18 +251,26 @@ private void maybeInitialize() throws IOException {
245251
if (initialized) {
246252
return;
247253
}
254+
initialize(0);
255+
}
256+
257+
private void initialize(long srcPos) throws IOException {
258+
checkState(!initialized);
248259
checkState(data == null);
249260
checkState(offset == 0);
250261
checkState(chunkCache == null);
251262
try {
263+
InputStream src = dataSupplier.get();
264+
ByteStreams.skipFully(src, srcPos);
252265
data =
253266
compressed
254-
? new ChunkerInputStream(new ZstdCompressingInputStream(dataSupplier.get()))
255-
: new ChunkerInputStream(dataSupplier.get());
267+
? new ChunkerInputStream(new ZstdCompressingInputStream(src))
268+
: new ChunkerInputStream(src);
256269
} catch (RuntimeException e) {
257270
Throwables.propagateIfPossible(e.getCause(), IOException.class);
258271
throw e;
259272
}
273+
offset = srcPos;
260274
initialized = true;
261275
}
262276

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

+22-27
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import java.io.IOException;
7070
import java.io.InputStream;
7171
import java.util.ArrayList;
72+
import java.util.Arrays;
7273
import java.util.Collections;
7374
import java.util.List;
7475
import java.util.Map;
@@ -357,23 +358,18 @@ public void progressiveCompressedUploadShouldWork() throws Exception {
357358
300,
358359
retrier);
359360

360-
byte[] blob = new byte[CHUNK_SIZE * 2 + 1];
361+
int chunkSize = 1024;
362+
int skipSize = chunkSize + 1;
363+
byte[] blob = new byte[chunkSize * 2 + 1];
361364
new Random().nextBytes(blob);
362365

363366
Chunker chunker =
364-
Chunker.builder().setInput(blob).setCompressed(true).setChunkSize(CHUNK_SIZE).build();
367+
Chunker.builder().setInput(blob).setCompressed(true).setChunkSize(chunkSize).build();
365368
HashCode hash = HashCode.fromString(DIGEST_UTIL.compute(blob).getHash());
366369

367-
while (chunker.hasNext()) {
368-
chunker.next();
369-
}
370-
long expectedSize = chunker.getOffset();
371-
chunker.reset();
372-
370+
ByteArrayOutputStream output = new ByteArrayOutputStream();
373371
serviceRegistry.addService(
374372
new ByteStreamImplBase() {
375-
376-
byte[] receivedData = new byte[(int) expectedSize];
377373
String receivedResourceName = null;
378374
boolean receivedComplete = false;
379375
long nextOffset = 0;
@@ -398,21 +394,21 @@ public void onNext(WriteRequest writeRequest) {
398394
assertThat(resourceName).isEmpty();
399395
}
400396

401-
assertThat(writeRequest.getWriteOffset()).isEqualTo(nextOffset);
402-
403-
ByteString data = writeRequest.getData();
404-
405-
System.arraycopy(
406-
data.toByteArray(), 0, receivedData, (int) nextOffset, data.size());
407-
408-
nextOffset += data.size();
409-
receivedComplete = expectedSize == nextOffset;
410-
assertThat(writeRequest.getFinishWrite()).isEqualTo(receivedComplete);
411-
412397
if (initialOffset == 0) {
413398
streamObserver.onError(Status.DEADLINE_EXCEEDED.asException());
414399
mustQueryWriteStatus = true;
415-
initialOffset = nextOffset;
400+
initialOffset = skipSize;
401+
nextOffset = initialOffset;
402+
} else {
403+
ByteString data = writeRequest.getData();
404+
try {
405+
data.writeTo(output);
406+
} catch (IOException e) {
407+
streamObserver.onError(e);
408+
return;
409+
}
410+
nextOffset += data.size();
411+
receivedComplete = writeRequest.getFinishWrite();
416412
}
417413
}
418414

@@ -423,10 +419,6 @@ public void onError(Throwable throwable) {
423419

424420
@Override
425421
public void onCompleted() {
426-
assertThat(nextOffset).isEqualTo(expectedSize);
427-
byte[] decompressed = Zstd.decompress(receivedData, blob.length);
428-
assertThat(decompressed).isEqualTo(blob);
429-
430422
WriteResponse response =
431423
WriteResponse.newBuilder().setCommittedSize(nextOffset).build();
432424
streamObserver.onNext(response);
@@ -444,7 +436,7 @@ public void queryWriteStatus(
444436
if (receivedResourceName != null && receivedResourceName.equals(resourceName)) {
445437
assertThat(mustQueryWriteStatus).isTrue();
446438
mustQueryWriteStatus = false;
447-
committedSize = nextOffset;
439+
committedSize = receivedComplete ? blob.length : skipSize;
448440
complete = receivedComplete;
449441
} else {
450442
committedSize = 0;
@@ -460,6 +452,9 @@ public void queryWriteStatus(
460452
});
461453

462454
uploader.uploadBlob(context, hash, chunker, true);
455+
byte[] decompressed = Zstd.decompress(output.toByteArray(), blob.length - skipSize);
456+
assertThat(Arrays.equals(decompressed, 0, decompressed.length, blob, skipSize, blob.length))
457+
.isTrue();
463458

464459
// This test should not have triggered any retries.
465460
Mockito.verify(mockBackoff, Mockito.never()).nextDelayMillis(any(Exception.class));

0 commit comments

Comments
 (0)