Skip to content

Commit 7817780

Browse files
bazel-iofmeum
andauthored
[8.6.0] Print name of files that fail to upload with INVALID_ARGUMENT (#28137) (#28241)
A major source of such failures are concurrent modifications, which are more easily debugged with the filename. Work towards #28134 Closes #28137. PiperOrigin-RevId: 855220277 Change-Id: I378f8bad0c94fc7328c4f74fbc6fa4ac26560a4d Commit fcaf0ba Co-authored-by: Fabian Meumertzheim <[email protected]>
1 parent fe199fb commit 7817780

File tree

3 files changed

+93
-8
lines changed

3 files changed

+93
-8
lines changed

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -485,13 +485,33 @@ private void releaseOut() {
485485
@Override
486486
public ListenableFuture<Void> uploadBlob(
487487
RemoteActionExecutionContext context, Digest digest, Blob blob) {
488-
return uploadChunker(
489-
context,
490-
digest,
491-
Chunker.builder()
492-
.setInput(digest.getSizeBytes(), blob)
493-
.setCompressed(shouldCompress(digest))
494-
.build());
488+
return Futures.catchingAsync(
489+
uploadChunker(
490+
context,
491+
digest,
492+
Chunker.builder()
493+
.setInput(digest.getSizeBytes(), blob)
494+
.setCompressed(shouldCompress(digest))
495+
.build()),
496+
IOException.class,
497+
e -> {
498+
var cause = e.getCause();
499+
if (!(cause instanceof StatusRuntimeException sre)) {
500+
return Futures.immediateFailedFuture(e);
501+
}
502+
var code = sre.getStatus().getCode();
503+
String blobDescription = blob.description();
504+
// INVALID_ARGUMENT is returned in case of a digest mismatch, which can hint at concurrent
505+
// modifications to the blob's source. Print it to help the user debug such issues.
506+
// https://github.com/bazelbuild/bazel/blob/ec36eacc31678ecf4b5c25f9ab7ab166330aff28/third_party/remoteapis/build/bazel/remote/execution/v2/remote_execution.proto#L283-L286
507+
if (code == Code.INVALID_ARGUMENT && blobDescription != null) {
508+
return Futures.immediateFailedFuture(
509+
new IOException(
510+
"while uploading %s: %s".formatted(blobDescription, e.getMessage()), e));
511+
}
512+
return Futures.immediateFailedFuture(e);
513+
},
514+
MoreExecutors.directExecutor());
495515
}
496516

497517
ListenableFuture<Void> uploadChunker(

src/main/java/com/google/devtools/build/lib/remote/common/RemoteCacheClient.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.io.InputStream;
2828
import java.io.OutputStream;
2929
import java.util.Set;
30+
import javax.annotation.Nullable;
3031

3132
/**
3233
* An interface for a remote caching protocol.
@@ -124,6 +125,12 @@ interface Blob extends Closeable {
124125
/** Get an input stream for the blob's data. Can be called multiple times. */
125126
InputStream get() throws IOException;
126127

128+
/** An optional human-readable description of the blob's source. */
129+
@Nullable
130+
default String description() {
131+
return null;
132+
}
133+
127134
@Override
128135
default void close() {}
129136
}
@@ -138,7 +145,20 @@ default void close() {}
138145
*/
139146
default ListenableFuture<Void> uploadFile(
140147
RemoteActionExecutionContext context, Digest digest, Path file) {
141-
return uploadBlob(context, digest, () -> new LazyFileInputStream(file));
148+
return uploadBlob(
149+
context,
150+
digest,
151+
new Blob() {
152+
@Override
153+
public InputStream get() {
154+
return new LazyFileInputStream(file);
155+
}
156+
157+
@Override
158+
public String description() {
159+
return "file " + file;
160+
}
161+
});
142162
}
143163

144164
/**

src/test/shell/bazel/remote/remote_execution_test.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3852,4 +3852,49 @@ EOF
38523852
expect_log "The file type of 'a/dir/symlink.txt' is not supported."
38533853
}
38543854

3855+
function do_test_concurrent_modification_during_upload() {
3856+
mkdir -p a
3857+
cat > a/BUILD <<'EOF'
3858+
genrule(
3859+
name = "gen_and_overwrite",
3860+
srcs = ["in"],
3861+
outs = ["out"],
3862+
# Non-hermetically overwrite the source file.
3863+
cmd = """
3864+
cat $< $< > $@
3865+
echo -n "overwritten" > $<
3866+
""",
3867+
tags = ["local"],
3868+
)
3869+
3870+
genrule(
3871+
name = "remote",
3872+
# Forced to run after gen_and_overwrite.
3873+
srcs = ["in", "out"],
3874+
outs = ["combined"],
3875+
cmd = "cat $(SRCS) > $@",
3876+
)
3877+
EOF
3878+
echo -n "$1" > a/in
3879+
3880+
bazel build \
3881+
--remote_executor=grpc://localhost:${worker_port} \
3882+
//a:remote >& $TEST_log && fail "build //a:remote should fail"
3883+
assert_contains "overwritten" a/in
3884+
expect_log "while uploading file .*/a/in:"
3885+
expect_log_n "a/in" 1
3886+
}
3887+
3888+
function test_concurrent_modification_during_upload_shorter() {
3889+
do_test_concurrent_modification_during_upload "very long content that will be overwritten"
3890+
}
3891+
3892+
function test_concurrent_modification_during_upload_longer() {
3893+
do_test_concurrent_modification_during_upload "short"
3894+
}
3895+
3896+
function test_concurrent_modification_during_upload_same_length() {
3897+
do_test_concurrent_modification_during_upload "same length"
3898+
}
3899+
38553900
run_suite "Remote execution and remote cache tests"

0 commit comments

Comments
 (0)