Skip to content

Commit 0177dbf

Browse files
authored
Fixed exception wrapping (#113)
1 parent a5def73 commit 0177dbf

21 files changed

+106
-88
lines changed

src/main/java/com/uber/cadence/activity/Activity.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ public static String getDomain() {
117117
*
118118
* @return never returns as always throws.
119119
*/
120-
public static RuntimeException throwWrapped(Throwable e) {
121-
return WorkflowInternal.throwWrapped(e);
120+
public static RuntimeException wrap(Exception e) {
121+
return WorkflowInternal.wrap(e);
122122
}
123123

124124
/**

src/main/java/com/uber/cadence/internal/sync/CheckedExceptionWrapper.java renamed to src/main/java/com/uber/cadence/internal/common/CheckedExceptionWrapper.java

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515
* permissions and limitations under the License.
1616
*/
1717

18-
package com.uber.cadence.internal.sync;
18+
package com.uber.cadence.internal.common;
1919

2020
import java.lang.reflect.Field;
2121
import java.lang.reflect.InvocationTargetException;
2222

2323
/**
2424
* Do not reference directly by the application level code.
25-
* Use {@link com.uber.cadence.workflow.Workflow#throwWrapped(Throwable)} inside a workflow code and
26-
* {@link com.uber.cadence.activity.Activity#throwWrapped(Throwable)} inside an activity code instead.
25+
* Use {@link com.uber.cadence.workflow.Workflow#wrap(Exception)} inside a workflow code and
26+
* {@link com.uber.cadence.activity.Activity#wrap(Exception)} inside an activity code instead.
2727
*/
28-
final class CheckedExceptionWrapper extends RuntimeException {
28+
public final class CheckedExceptionWrapper extends RuntimeException {
2929

3030
private final static Field causeField;
3131

@@ -39,39 +39,25 @@ final class CheckedExceptionWrapper extends RuntimeException {
3939
}
4040

4141
/**
42-
* Throws CheckedExceptionWrapper if e is checked exception.
42+
* Returns CheckedExceptionWrapper if e is checked exception.
4343
* If there is a need to return a checked exception from an activity or workflow implementation
44-
* throwWrapped it using this method. The library code will unwrap it automatically when propagating exception
44+
* throw a wrapped exception it using this method. The library code will unwrap it automatically when propagating exception
4545
* to the caller.
46-
* <p>
47-
* Throws original exception if e is {@link RuntimeException} or {@link Error}.
48-
* Never returns. But return type is not empty to be able to use it as:
4946
* <pre>
5047
* try {
5148
* return someCall();
5249
* } catch (Exception e) {
53-
* throw CheckedExceptionWrapper.throwWrapped(e);
50+
* throw CheckedExceptionWrapper.wrap(e);
5451
* }
5552
* </pre>
56-
* If throwWrapped returned void it wouldn't be possible to write <code>throw CheckedExcptionWrapper.throwWrapped</code>
57-
* and compiler would complain about missing return.
58-
*
59-
* @return never returns as always throws.
6053
*/
61-
public static RuntimeException throwWrapped(Throwable e) {
62-
throw getWrapped(e);
63-
}
64-
65-
/**
66-
* Similar to throwWrapped but just returns wrapped error without throwing it.
67-
* Only Error is thrown as it should propagate up the stack without any delay.
68-
*/
69-
public static RuntimeException getWrapped(Throwable e) {
54+
public static RuntimeException wrap(Throwable e) {
55+
// Errors are expected to propagate without any handling.
7056
if (e instanceof Error) {
7157
throw (Error) e;
7258
}
7359
if (e instanceof InvocationTargetException) {
74-
return getWrapped(e.getCause());
60+
return wrap(e.getCause());
7561
}
7662
if (e instanceof RuntimeException) {
7763
return (RuntimeException) e;
@@ -83,7 +69,7 @@ public static RuntimeException getWrapped(Throwable e) {
8369
* Removes CheckedException wrapper from the whole chain of Exceptions.
8470
* Assumes that wrapper always has a cause which cannot be a wrapper.
8571
*/
86-
public static Throwable unwrap(Throwable e) {
72+
public static Exception unwrap(Exception e) {
8773
Throwable head = e;
8874
if (head instanceof CheckedExceptionWrapper) {
8975
head = head.getCause();
@@ -98,7 +84,11 @@ public static Throwable unwrap(Throwable e) {
9884
tail = current;
9985
current = tail.getCause();
10086
}
101-
return head;
87+
if (head instanceof Error) {
88+
// Error should be propagated without any handling.
89+
throw (Error)head;
90+
}
91+
return (Exception) head;
10292
}
10393

10494
/**

src/main/java/com/uber/cadence/internal/common/WorkflowExecutionUtils.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import com.uber.cadence.client.WorkflowTerminatedException;
2323
import com.uber.cadence.client.WorkflowTimedOutException;
2424
import com.uber.cadence.common.RetryOptions;
25-
import com.uber.cadence.workflow.Workflow;
2625
import org.apache.thrift.TException;
2726

2827
import java.lang.reflect.InvocationTargetException;
@@ -110,8 +109,7 @@ public static HistoryEvent getInstanceCloseEvent(Iface service, String domain,
110109
response = SynchronousRetryer.retryWithResult(retryParameters,
111110
() -> service.GetWorkflowExecutionHistory(r));
112111
} catch (TException e) {
113-
// TODO: Refactor to avoid this ugly circular dependency.
114-
throw Workflow.throwWrapped(e);
112+
throw CheckedExceptionWrapper.wrap(e);
115113
}
116114
if (timeout != 0 && System.currentTimeMillis() - start > unit.toMillis(timeout)) {
117115
throw new TimeoutException("WorkflowId=" + workflowExecution.getWorkflowId() +

src/main/java/com/uber/cadence/internal/replay/ReplayDecider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ private void eventLoop() {
215215
}
216216
completed = true;
217217
} catch (Throwable e) {
218-
failure = workflow.mapUnexpectedException(e);
218+
// can cast as Error is caught above.
219+
failure = workflow.mapUnexpectedException((Exception) e);
219220
completed = true;
220221
}
221222
}

src/main/java/com/uber/cadence/internal/replay/ReplayWorkflow.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,5 @@ public interface ReplayWorkflow {
6262
* @param failure Unexpected failure cause
6363
* @return Serialized failure
6464
*/
65-
WorkflowExecutionException mapUnexpectedException(Throwable failure);
65+
WorkflowExecutionException mapUnexpectedException(Exception failure);
6666
}

src/main/java/com/uber/cadence/internal/sync/ActivityInvocationHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public Object invoke(Object proxy, Method method, Object[] args) {
4848
return "ActivityInvocationHandler";
4949
}
5050
} catch (NoSuchMethodException e) {
51-
throw Workflow.throwWrapped(e);
51+
throw Workflow.wrap(e);
5252
}
5353
if (!method.getDeclaringClass().isInterface()) {
5454
throw new IllegalArgumentException("Interface type is expected: " + method.getDeclaringClass());

src/main/java/com/uber/cadence/internal/sync/AllOfFuture.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,9 @@ public List<G> get(long timeout, TimeUnit unit) throws TimeoutException {
126126
public List<G> get(long timeout, TimeUnit unit, List<G> defaultValue) {
127127
return impl.get(timeout, unit, defaultValue);
128128
}
129+
130+
@Override
131+
public RuntimeException getFailure() {
132+
return impl.getFailure();
133+
}
129134
}

src/main/java/com/uber/cadence/internal/sync/AsyncInternal.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@ private static <R> Promise<R> execute(boolean async, Functions.Func<R> func) {
247247
try {
248248
func.apply();
249249
return getAsyncInvocationResult();
250-
} catch (Throwable e) {
251-
return Workflow.newFailedPromise(Workflow.getWrapped(e));
250+
} catch (Exception e) {
251+
return Workflow.newFailedPromise(Workflow.wrap(e));
252252
} finally {
253253
closeAsyncInvocation();
254254
}
@@ -257,8 +257,8 @@ private static <R> Promise<R> execute(boolean async, Functions.Func<R> func) {
257257
WorkflowInternal.newThread(false, () -> {
258258
try {
259259
result.complete(func.apply());
260-
} catch (Throwable e) {
261-
result.completeExceptionally(Workflow.getWrapped(e));
260+
} catch (Exception e) {
261+
result.completeExceptionally(Workflow.wrap(e));
262262
}
263263
}).start();
264264
return result;

src/main/java/com/uber/cadence/internal/sync/CompletablePromiseImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ public V get(long timeout, TimeUnit unit, V defaultValue) {
128128
return value;
129129
}
130130

131+
@Override
132+
public RuntimeException getFailure() {
133+
if (!completed) {
134+
WorkflowThread.yield("Feature.get", () -> completed);
135+
}
136+
if (failure != null) {
137+
unregisterWithRunner();
138+
return failure;
139+
}
140+
return null;
141+
}
142+
131143
private void unregisterWithRunner() {
132144
if (registeredWithRunner) {
133145
runner.forgetFailedPromise(this);

src/main/java/com/uber/cadence/internal/sync/DeterministicRunnerImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.uber.cadence.internal.sync;
1919

20+
import com.uber.cadence.internal.common.CheckedExceptionWrapper;
2021
import com.uber.cadence.workflow.Promise;
2122
import org.slf4j.Logger;
2223
import org.slf4j.LoggerFactory;

0 commit comments

Comments
 (0)