Skip to content

Commit f4c3777

Browse files
committed
initial commit
1 parent ebebbc9 commit f4c3777

File tree

5 files changed

+691
-20
lines changed

5 files changed

+691
-20
lines changed

client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,8 @@ public void startAndBlock() {
228228
activityRequest.getInput().getValue(),
229229
activityRequest.getTaskId());
230230
} catch (Throwable e) {
231-
failureDetails = TaskFailureDetails.newBuilder()
232-
.setErrorType(e.getClass().getName())
233-
.setErrorMessage(e.getMessage())
234-
.setStackTrace(StringValue.of(FailureDetails.getFullStackTrace(e)))
235-
.build();
231+
failureDetails = new FailureDetails(
232+
e instanceof Exception ? (Exception) e : new RuntimeException(e)).toProto();
236233
}
237234

238235
ActivityResponse.Builder responseBuilder = ActivityResponse.newBuilder()

client/src/main/java/com/microsoft/durabletask/FailureDetails.java

Lines changed: 136 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
// Licensed under the MIT License.
33
package com.microsoft.durabletask;
44

5+
import com.google.protobuf.NullValue;
56
import com.google.protobuf.StringValue;
7+
import com.google.protobuf.Value;
68
import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.TaskFailureDetails;
79

810
import javax.annotation.Nonnull;
911
import javax.annotation.Nullable;
12+
import java.util.Collections;
13+
import java.util.HashMap;
14+
import java.util.Map;
1015

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

2431
FailureDetails(
2532
String errorType,
2633
@Nullable String errorMessage,
2734
@Nullable String errorDetails,
2835
boolean isNonRetriable) {
36+
this(errorType, errorMessage, errorDetails, isNonRetriable, null, null);
37+
}
38+
39+
FailureDetails(
40+
String errorType,
41+
@Nullable String errorMessage,
42+
@Nullable String errorDetails,
43+
boolean isNonRetriable,
44+
@Nullable FailureDetails innerFailure,
45+
@Nullable Map<String, Object> properties) {
2946
this.errorType = errorType;
3047
this.stackTrace = errorDetails;
3148

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

3756
FailureDetails(Exception exception) {
38-
this(exception.getClass().getName(), exception.getMessage(), getFullStackTrace(exception), false);
57+
this(exception.getClass().getName(),
58+
exception.getMessage(),
59+
getFullStackTrace(exception),
60+
false,
61+
exception.getCause() != null ? fromExceptionRecursive(exception.getCause()) : null,
62+
null);
3963
}
4064

4165
FailureDetails(TaskFailureDetails proto) {
4266
this(proto.getErrorType(),
4367
proto.getErrorMessage(),
4468
proto.getStackTrace().getValue(),
45-
proto.getIsNonRetriable());
69+
proto.getIsNonRetriable(),
70+
proto.hasInnerFailure() ? new FailureDetails(proto.getInnerFailure()) : null,
71+
convertProtoProperties(proto.getPropertiesMap()));
4672
}
4773

4874
/**
@@ -86,6 +112,28 @@ public boolean isNonRetriable() {
86112
return this.isNonRetriable;
87113
}
88114

115+
/**
116+
* Gets the inner failure that caused this failure, or {@code null} if there is no inner cause.
117+
*
118+
* @return the inner {@code FailureDetails} or {@code null}
119+
*/
120+
@Nullable
121+
public FailureDetails getInnerFailure() {
122+
return this.innerFailure;
123+
}
124+
125+
/**
126+
* Gets additional properties associated with the exception, or {@code null} if no properties are available.
127+
* <p>
128+
* The returned map is unmodifiable.
129+
*
130+
* @return an unmodifiable map of property names to values, or {@code null}
131+
*/
132+
@Nullable
133+
public Map<String, Object> getProperties() {
134+
return this.properties;
135+
}
136+
89137
/**
90138
* Returns {@code true} if the task failure was provided by the specified exception type, otherwise {@code false}.
91139
* <p>
@@ -112,6 +160,11 @@ public boolean isCausedBy(Class<? extends Exception> exceptionClass) {
112160
}
113161
}
114162

163+
@Override
164+
public String toString() {
165+
return this.errorType + ": " + this.errorMessage;
166+
}
167+
115168
static String getFullStackTrace(Throwable e) {
116169
StackTraceElement[] elements = e.getStackTrace();
117170

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

126179
TaskFailureDetails toProto() {
127-
return TaskFailureDetails.newBuilder()
180+
TaskFailureDetails.Builder builder = TaskFailureDetails.newBuilder()
128181
.setErrorType(this.getErrorType())
129182
.setErrorMessage(this.getErrorMessage())
130183
.setStackTrace(StringValue.of(this.getStackTrace() != null ? this.getStackTrace() : ""))
131-
.build();
184+
.setIsNonRetriable(this.isNonRetriable);
185+
186+
if (this.innerFailure != null) {
187+
builder.setInnerFailure(this.innerFailure.toProto());
188+
}
189+
190+
if (this.properties != null) {
191+
builder.putAllProperties(convertToProtoProperties(this.properties));
192+
}
193+
194+
return builder.build();
195+
}
196+
197+
@Nullable
198+
private static FailureDetails fromExceptionRecursive(@Nullable Throwable exception) {
199+
if (exception == null) {
200+
return null;
201+
}
202+
return new FailureDetails(
203+
exception.getClass().getName(),
204+
exception.getMessage(),
205+
getFullStackTrace(exception),
206+
false,
207+
exception.getCause() != null ? fromExceptionRecursive(exception.getCause()) : null,
208+
null);
209+
}
210+
211+
@Nullable
212+
private static Map<String, Object> convertProtoProperties(Map<String, Value> protoProperties) {
213+
if (protoProperties == null || protoProperties.isEmpty()) {
214+
return null;
215+
}
216+
217+
Map<String, Object> result = new HashMap<>();
218+
for (Map.Entry<String, Value> entry : protoProperties.entrySet()) {
219+
result.put(entry.getKey(), convertProtoValue(entry.getValue()));
220+
}
221+
return result;
222+
}
223+
224+
@Nullable
225+
private static Object convertProtoValue(Value value) {
226+
if (value == null) {
227+
return null;
228+
}
229+
switch (value.getKindCase()) {
230+
case NULL_VALUE:
231+
return null;
232+
case NUMBER_VALUE:
233+
return value.getNumberValue();
234+
case STRING_VALUE:
235+
return value.getStringValue();
236+
case BOOL_VALUE:
237+
return value.getBoolValue();
238+
default:
239+
return value.toString();
240+
}
241+
}
242+
243+
private static Map<String, Value> convertToProtoProperties(Map<String, Object> properties) {
244+
Map<String, Value> result = new HashMap<>();
245+
for (Map.Entry<String, Object> entry : properties.entrySet()) {
246+
result.put(entry.getKey(), convertToProtoValue(entry.getValue()));
247+
}
248+
return result;
249+
}
250+
251+
private static Value convertToProtoValue(@Nullable Object obj) {
252+
if (obj == null) {
253+
return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
254+
} else if (obj instanceof Number) {
255+
return Value.newBuilder().setNumberValue(((Number) obj).doubleValue()).build();
256+
} else if (obj instanceof Boolean) {
257+
return Value.newBuilder().setBoolValue((Boolean) obj).build();
258+
} else if (obj instanceof String) {
259+
return Value.newBuilder().setStringValue((String) obj).build();
260+
} else {
261+
return Value.newBuilder().setStringValue(obj.toString()).build();
262+
}
132263
}
133-
}
264+
}

0 commit comments

Comments
 (0)