Skip to content

Commit 86e8b56

Browse files
committed
Include causal status details in higher-level statuses
When an operation fails and we want to produce a new status at a higher level, we commonly are turning the first status into an exception to attach to the new exception. We should instead prefer to keep as much information in the status description itself, as cause is not as reliable to be logged/propagated. I do expect long-term we'll want to expose an API in grpc-api for this, but for the moment let's keep it internal. In particular, we'd have to figure out its name. I could also believe we might want different formatting, which becomes a clearer discussion when we can see the usages. I'm pretty certain there are some other places that could benefit from this utility, as I remember really wishing I had these functions a month or two ago. But these are the places I could immediately find. OutlierDetectionLoadBalancerConfig had its status code changed from INTERNAL to UNAVAILABLE because the value comes externally, and so isn't a gRPC bug or such. I didn't change the xds policies in the same way because it's murkier as the configuration for those is largely generated within xds itself.
1 parent 27d1508 commit 86e8b56

File tree

6 files changed

+44
-21
lines changed

6 files changed

+44
-21
lines changed

core/src/main/java/io/grpc/internal/GrpcUtil.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,31 @@ public static Status replaceInappropriateControlPlaneStatus(Status status) {
821821
+ status.getDescription()).withCause(status.getCause()) : status;
822822
}
823823

824+
/**
825+
* Returns a "clean" representation of a status code and description (not cause) like
826+
* "UNAVAILABLE: The description". Should be similar to Status.formatThrowableMessage().
827+
*/
828+
public static String statusToPrettyString(Status status) {
829+
if (status.getDescription() == null) {
830+
return status.getCode().toString();
831+
} else {
832+
return status.getCode() + ": " + status.getDescription();
833+
}
834+
}
835+
836+
/**
837+
* Create a status with contextual information, propagating details from a non-null status that
838+
* contributed to the failure. For example, if UNAVAILABLE, "Couldn't load bar", and status
839+
* "FAILED_PRECONDITION: Foo missing" were passed as arguments, then this method would produce the
840+
* status "UNAVAILABLE: Couldn't load bar: FAILED_PRECONDITION: Foo missing" with a cause if the
841+
* passed status had a cause.
842+
*/
843+
public static Status statusWithDetails(Status.Code code, String description, Status causeStatus) {
844+
return code.toStatus()
845+
.withDescription(description + ": " + statusToPrettyString(causeStatus))
846+
.withCause(causeStatus.getCause());
847+
}
848+
824849
/**
825850
* Checks whether the given item exists in the iterable. This is copied from Guava Collect's
826851
* {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't

core/src/main/java/io/grpc/internal/RetriableStream.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -938,9 +938,8 @@ public void run() {
938938
&& localOnlyTransparentRetries.incrementAndGet() > 1_000) {
939939
commitAndRun(substream);
940940
if (state.winningSubstream == substream) {
941-
Status tooManyTransparentRetries = Status.INTERNAL
942-
.withDescription("Too many transparent retries. Might be a bug in gRPC")
943-
.withCause(status.asRuntimeException());
941+
Status tooManyTransparentRetries = GrpcUtil.statusWithDetails(
942+
Status.Code.INTERNAL, "Too many transparent retries. Might be a bug in gRPC", status);
944943
safeCloseMasterListener(tooManyTransparentRetries, rpcProgress, trailers);
945944
}
946945
return;

opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.grpc.ServerCallHandler;
3737
import io.grpc.ServerInterceptor;
3838
import io.grpc.ServerStreamTracer;
39+
import io.grpc.internal.GrpcUtil;
3940
import io.grpc.opentelemetry.internal.OpenTelemetryConstants;
4041
import io.opentelemetry.api.OpenTelemetry;
4142
import io.opentelemetry.api.common.AttributesBuilder;
@@ -463,19 +464,11 @@ private void recordInboundMessageSize(Span span, int seqNo, long bytes) {
463464
span.addEvent("Inbound message", attributesBuilder.build());
464465
}
465466

466-
private String generateErrorStatusDescription(io.grpc.Status status) {
467-
if (status.getDescription() != null) {
468-
return status.getCode() + ": " + status.getDescription();
469-
} else {
470-
return status.getCode().toString();
471-
}
472-
}
473-
474467
private void endSpanWithStatus(Span span, io.grpc.Status status) {
475468
if (status.isOk()) {
476469
span.setStatus(StatusCode.OK);
477470
} else {
478-
span.setStatus(StatusCode.ERROR, generateErrorStatusDescription(status));
471+
span.setStatus(StatusCode.ERROR, GrpcUtil.statusToPrettyString(status));
479472
}
480473
span.end();
481474
}

util/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.grpc.LoadBalancerProvider;
2424
import io.grpc.NameResolver.ConfigOrError;
2525
import io.grpc.Status;
26+
import io.grpc.internal.GrpcUtil;
2627
import io.grpc.internal.JsonUtil;
2728
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig;
2829
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.FailurePercentageEjection;
@@ -148,9 +149,10 @@ private ConfigOrError parseLoadBalancingPolicyConfigInternal(Map<String, ?> rawC
148149
ConfigOrError childConfig = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig(
149150
JsonUtil.getListOfObjects(rawConfig, "childPolicy"));
150151
if (childConfig.getError() != null) {
151-
return ConfigOrError.fromError(Status.INTERNAL
152-
.withDescription("Failed to parse child in outlier_detection_experimental: " + rawConfig)
153-
.withCause(childConfig.getError().asRuntimeException()));
152+
return ConfigOrError.fromError(GrpcUtil.statusWithDetails(
153+
Status.Code.UNAVAILABLE,
154+
"Failed to parse child in outlier_detection_experimental",
155+
childConfig.getError()));
154156
}
155157
configBuilder.setChildConfig(childConfig.getConfig());
156158

xds/src/main/java/io/grpc/xds/WeightedTargetLoadBalancerProvider.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.grpc.LoadBalancerRegistry;
2626
import io.grpc.NameResolver.ConfigOrError;
2727
import io.grpc.Status;
28+
import io.grpc.internal.GrpcUtil;
2829
import io.grpc.internal.JsonUtil;
2930
import io.grpc.util.GracefulSwitchLoadBalancer;
3031
import java.util.LinkedHashMap;
@@ -99,9 +100,10 @@ public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawConfig) {
99100
ConfigOrError childConfig = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig(
100101
JsonUtil.getListOfObjects(rawWeightedTarget, "childPolicy"), lbRegistry);
101102
if (childConfig.getError() != null) {
102-
return ConfigOrError.fromError(Status.INTERNAL
103-
.withDescription("Could not parse weighted_target's child policy:" + name)
104-
.withCause(childConfig.getError().asRuntimeException()));
103+
return ConfigOrError.fromError(GrpcUtil.statusWithDetails(
104+
Status.Code.INTERNAL,
105+
"Could not parse weighted_target's child policy: " + name,
106+
childConfig.getError()));
105107
}
106108
parsedChildConfigs.put(name, new WeightedPolicySelection(weight, childConfig.getConfig()));
107109
}

xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancerProvider.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.grpc.LoadBalancerRegistry;
2424
import io.grpc.NameResolver.ConfigOrError;
2525
import io.grpc.Status;
26+
import io.grpc.internal.GrpcUtil;
2627
import io.grpc.internal.JsonUtil;
2728
import io.grpc.util.GracefulSwitchLoadBalancer;
2829
import io.grpc.xds.WrrLocalityLoadBalancer.WrrLocalityConfig;
@@ -62,9 +63,10 @@ public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawConfig) {
6263
ConfigOrError childConfig = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig(
6364
JsonUtil.getListOfObjects(rawConfig, "childPolicy"));
6465
if (childConfig.getError() != null) {
65-
return ConfigOrError.fromError(Status.INTERNAL
66-
.withDescription("Failed to parse child policy in wrr_locality LB policy: " + rawConfig)
67-
.withCause(childConfig.getError().asRuntimeException()));
66+
return ConfigOrError.fromError(GrpcUtil.statusWithDetails(
67+
Status.Code.INTERNAL,
68+
"Failed to parse child policy in wrr_locality LB policy",
69+
childConfig.getError()));
6870
}
6971
return ConfigOrError.fromConfig(new WrrLocalityConfig(childConfig.getConfig()));
7072
} catch (RuntimeException e) {

0 commit comments

Comments
 (0)