Skip to content

Commit 7cd8a94

Browse files
committed
update
1 parent af13f66 commit 7cd8a94

3 files changed

Lines changed: 57 additions & 6 deletions

File tree

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,8 @@ public void startAndBlock() {
392392
activityRequest.getTaskId());
393393
} catch (Throwable e) {
394394
activityError = e;
395-
Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
396395
failureDetails = FailureDetails.fromException(
397-
ex, this.exceptionPropertiesProvider).toProto();
396+
e, this.exceptionPropertiesProvider).toProto();
398397
} finally {
399398
activityScope.close();
400399
TracingHelper.endSpan(activitySpan, activityError);

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,21 @@ public final class FailureDetails {
5858
}
5959

6060
/**
61-
* Creates a {@code FailureDetails} from an exception, optionally using the provided
61+
* Creates a {@code FailureDetails} from a throwable, optionally using the provided
6262
* {@link ExceptionPropertiesProvider} to extract custom properties.
63+
* <p>
64+
* Accepts any {@link Throwable} so callers that catch {@code Throwable} (for example, activity
65+
* dispatchers that need to report {@link Error} subclasses such as {@link StackOverflowError}
66+
* or {@link OutOfMemoryError}) can preserve the original error type instead of having to wrap
67+
* it in a {@link RuntimeException}. The {@link ExceptionPropertiesProvider}, however, is only
68+
* invoked for {@link Exception} instances, since that is what its contract accepts.
6369
*
64-
* @param exception the exception that caused the failure
70+
* @param throwable the throwable that caused the failure
6571
* @param provider the provider for extracting custom properties, or {@code null}
6672
* @return a new {@code FailureDetails} instance
6773
*/
68-
static FailureDetails fromException(Exception exception, @Nullable ExceptionPropertiesProvider provider) {
69-
return fromExceptionRecursive(exception, provider, 0);
74+
static FailureDetails fromException(Throwable throwable, @Nullable ExceptionPropertiesProvider provider) {
75+
return fromExceptionRecursive(throwable, provider, 0);
7076
}
7177

7278
FailureDetails(TaskFailureDetails proto) {

client/src/test/java/com/microsoft/durabletask/FailureDetailsTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,52 @@ void fromException_providerReturnsNull_noProperties() {
206206
assertNull(details.getProperties());
207207
}
208208

209+
@Test
210+
void fromException_acceptsErrorAndPreservesType() {
211+
// Errors aren't Exceptions, but activity dispatchers catch Throwable. fromException should
212+
// accept them so the original error type (e.g. StackOverflowError) is reported instead of
213+
// being hidden behind a synthetic RuntimeException wrapper.
214+
StackOverflowError error = new StackOverflowError("too deep");
215+
216+
FailureDetails details = FailureDetails.fromException(error, null);
217+
218+
assertEquals("java.lang.StackOverflowError", details.getErrorType());
219+
assertEquals("too deep", details.getErrorMessage());
220+
assertNull(details.getProperties());
221+
}
222+
223+
@Test
224+
void fromException_errorWithCause_preservesInnerFailure() {
225+
IOException cause = new IOException("disk full");
226+
OutOfMemoryError error = new OutOfMemoryError("heap exhausted");
227+
error.initCause(cause);
228+
229+
FailureDetails details = FailureDetails.fromException(error, null);
230+
231+
assertEquals("java.lang.OutOfMemoryError", details.getErrorType());
232+
assertNotNull(details.getInnerFailure());
233+
assertEquals("java.io.IOException", details.getInnerFailure().getErrorType());
234+
assertEquals("disk full", details.getInnerFailure().getErrorMessage());
235+
}
236+
237+
@Test
238+
void fromException_errorWithProvider_skipsProviderForError() {
239+
// The provider contract takes Exception, not Throwable, so for an Error we shouldn't
240+
// call the provider at all. The Error itself still needs to be reported faithfully.
241+
ExceptionPropertiesProvider provider = exception -> {
242+
Map<String, Object> props = new HashMap<>();
243+
props.put("called", true);
244+
return props;
245+
};
246+
247+
StackOverflowError error = new StackOverflowError("too deep");
248+
249+
FailureDetails details = FailureDetails.fromException(error, provider);
250+
251+
assertEquals("java.lang.StackOverflowError", details.getErrorType());
252+
assertNull(details.getProperties());
253+
}
254+
209255
@Test
210256
void fromException_providerSelectivelyReturnsProperties() {
211257
ExceptionPropertiesProvider provider = exception -> {

0 commit comments

Comments
 (0)