Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,8 @@ public void startAndBlock() {
activityRequest.getInput().getValue(),
activityRequest.getTaskId());
} catch (Throwable e) {
failureDetails = TaskFailureDetails.newBuilder()
.setErrorType(e.getClass().getName())
.setErrorMessage(e.getMessage())
.setStackTrace(StringValue.of(FailureDetails.getFullStackTrace(e)))
.build();
failureDetails = new FailureDetails(
e instanceof Exception ? (Exception) e : new RuntimeException(e)).toProto();
}

ActivityResponse.Builder responseBuilder = ActivityResponse.newBuilder()
Expand Down
141 changes: 136 additions & 5 deletions client/src/main/java/com/microsoft/durabletask/FailureDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
// Licensed under the MIT License.
package com.microsoft.durabletask;

import com.google.protobuf.NullValue;
import com.google.protobuf.StringValue;
import com.google.protobuf.Value;
import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.TaskFailureDetails;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* Class that represents the details of a task failure.
Expand All @@ -20,29 +25,50 @@ public final class FailureDetails {
private final String errorMessage;
private final String stackTrace;
private final boolean isNonRetriable;
private final FailureDetails innerFailure;
private final Map<String, Object> properties;

FailureDetails(
String errorType,
@Nullable String errorMessage,
@Nullable String errorDetails,
boolean isNonRetriable) {
this(errorType, errorMessage, errorDetails, isNonRetriable, null, null);
}

FailureDetails(
String errorType,
@Nullable String errorMessage,
@Nullable String errorDetails,
boolean isNonRetriable,
@Nullable FailureDetails innerFailure,
@Nullable Map<String, Object> properties) {
this.errorType = errorType;
this.stackTrace = errorDetails;

// Error message can be null for things like NullPointerException but the gRPC contract doesn't allow null
this.errorMessage = errorMessage != null ? errorMessage : "";
this.isNonRetriable = isNonRetriable;
this.innerFailure = innerFailure;
this.properties = properties != null ? Collections.unmodifiableMap(new HashMap<>(properties)) : null;
}

FailureDetails(Exception exception) {
Comment thread
nytian marked this conversation as resolved.
Outdated
this(exception.getClass().getName(), exception.getMessage(), getFullStackTrace(exception), false);
this(exception.getClass().getName(),
exception.getMessage(),
getFullStackTrace(exception),
false,
exception.getCause() != null ? fromExceptionRecursive(exception.getCause()) : null,
null);
}

FailureDetails(TaskFailureDetails proto) {
this(proto.getErrorType(),
proto.getErrorMessage(),
proto.getStackTrace().getValue(),
proto.getIsNonRetriable());
proto.getIsNonRetriable(),
proto.hasInnerFailure() ? new FailureDetails(proto.getInnerFailure()) : null,
Comment thread
bachuv marked this conversation as resolved.
convertProtoProperties(proto.getPropertiesMap()));
}

/**
Expand Down Expand Up @@ -86,6 +112,28 @@ public boolean isNonRetriable() {
return this.isNonRetriable;
}

/**
* Gets the inner failure that caused this failure, or {@code null} if there is no inner cause.
*
* @return the inner {@code FailureDetails} or {@code null}
*/
@Nullable
public FailureDetails getInnerFailure() {
return this.innerFailure;
}

/**
* Gets additional properties associated with the exception, or {@code null} if no properties are available.
* <p>
* The returned map is unmodifiable.
*
* @return an unmodifiable map of property names to values, or {@code null}
*/
@Nullable
public Map<String, Object> getProperties() {
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
return this.properties;
}

/**
* Returns {@code true} if the task failure was provided by the specified exception type, otherwise {@code false}.
* <p>
Expand All @@ -112,6 +160,11 @@ public boolean isCausedBy(Class<? extends Exception> exceptionClass) {
}
}
Comment thread
nytian marked this conversation as resolved.

@Override
public String toString() {
return this.errorType + ": " + this.errorMessage;
}

static String getFullStackTrace(Throwable e) {
StackTraceElement[] elements = e.getStackTrace();

Expand All @@ -124,10 +177,88 @@ static String getFullStackTrace(Throwable e) {
}

TaskFailureDetails toProto() {
return TaskFailureDetails.newBuilder()
TaskFailureDetails.Builder builder = TaskFailureDetails.newBuilder()
.setErrorType(this.getErrorType())
.setErrorMessage(this.getErrorMessage())
.setStackTrace(StringValue.of(this.getStackTrace() != null ? this.getStackTrace() : ""))
.build();
.setIsNonRetriable(this.isNonRetriable);

if (this.innerFailure != null) {
builder.setInnerFailure(this.innerFailure.toProto());
}

if (this.properties != null) {
builder.putAllProperties(convertToProtoProperties(this.properties));
}

return builder.build();
}

@Nullable
private static FailureDetails fromExceptionRecursive(@Nullable Throwable exception) {
if (exception == null) {
return null;
}
return new FailureDetails(
exception.getClass().getName(),
exception.getMessage(),
getFullStackTrace(exception),
false,
exception.getCause() != null ? fromExceptionRecursive(exception.getCause()) : null,
null);
}

@Nullable
private static Map<String, Object> convertProtoProperties(Map<String, Value> protoProperties) {
if (protoProperties == null || protoProperties.isEmpty()) {
return null;
}

Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, Value> entry : protoProperties.entrySet()) {
result.put(entry.getKey(), convertProtoValue(entry.getValue()));
}
return result;
}

@Nullable
private static Object convertProtoValue(Value value) {
if (value == null) {
return null;
}
switch (value.getKindCase()) {
case NULL_VALUE:
return null;
case NUMBER_VALUE:
return value.getNumberValue();
case STRING_VALUE:
return value.getStringValue();
case BOOL_VALUE:
return value.getBoolValue();
default:
return value.toString();
}
Comment thread
nytian marked this conversation as resolved.
}

private static Map<String, Value> convertToProtoProperties(Map<String, Object> properties) {
Map<String, Value> result = new HashMap<>();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
result.put(entry.getKey(), convertToProtoValue(entry.getValue()));
}
return result;
}

private static Value convertToProtoValue(@Nullable Object obj) {
if (obj == null) {
return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
} else if (obj instanceof Number) {
return Value.newBuilder().setNumberValue(((Number) obj).doubleValue()).build();
} else if (obj instanceof Boolean) {
return Value.newBuilder().setBoolValue((Boolean) obj).build();
} else if (obj instanceof String) {
return Value.newBuilder().setStringValue((String) obj).build();
} else {
return Value.newBuilder().setStringValue(obj.toString()).build();
}
}
}
}
Loading
Loading