Skip to content

Commit 27ba155

Browse files
authored
Fail workflow task on unexpected exceptions (#199)
* Fail workflow task on unexpected exceptions * Code cleanup
1 parent 2f7f9dd commit 27ba155

16 files changed

+213
-164
lines changed

src/main/java/io/temporal/internal/replay/NonDeterministicWorkflowError.java renamed to src/main/java/io/temporal/internal/replay/InternalWorkflowTaskException.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919

2020
package io.temporal.internal.replay;
2121

22-
public final class NonDeterministicWorkflowError extends Error {
22+
public final class InternalWorkflowTaskException extends RuntimeException {
2323

24-
public NonDeterministicWorkflowError(String message, Throwable cause) {
24+
public InternalWorkflowTaskException(String message, Throwable cause) {
2525
super(message, cause);
2626
}
27+
28+
public InternalWorkflowTaskException(Throwable cause) {
29+
super(cause);
30+
}
2731
}

src/main/java/io/temporal/internal/replay/ReplayWorkflow.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public interface ReplayWorkflow {
3333
/** Handle an external signal event. */
3434
void handleSignal(String signalName, Optional<Payloads> input, long eventId);
3535

36-
boolean eventLoop() throws Throwable;
36+
boolean eventLoop();
3737

3838
/** @return null means no output yet */
3939
Optional<Payloads> getOutput();
@@ -61,7 +61,5 @@ public interface ReplayWorkflow {
6161
*/
6262
WorkflowExecutionException mapUnexpectedException(Throwable failure);
6363

64-
WorkflowExecutionException mapError(Throwable failure);
65-
6664
WorkflowImplementationOptions getWorkflowImplementationOptions();
6765
}

src/main/java/io/temporal/internal/replay/ReplayWorkflowExecutor.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ public void eventLoop() {
7272
}
7373
try {
7474
completed = workflow.eventLoop();
75-
} catch (Error e) {
76-
throw e;
7775
} catch (WorkflowExecutionException e) {
7876
failure = e;
7977
completed = true;
@@ -82,10 +80,6 @@ public void eventLoop() {
8280
failure = workflow.mapUnexpectedException(e);
8381
}
8482
completed = true;
85-
} catch (Throwable e) {
86-
// can cast as Error is caught above.
87-
failure = workflow.mapUnexpectedException(e);
88-
completed = true;
8983
}
9084
if (completed) {
9185
completeWorkflow();
@@ -147,15 +141,15 @@ public WorkflowImplementationOptions getWorkflowImplementationOptions() {
147141
return workflow.getWorkflowImplementationOptions();
148142
}
149143

150-
public WorkflowExecutionException mapError(Throwable e) {
151-
return workflow.mapError(e);
152-
}
153-
154144
public void close() {
155145
workflow.close();
156146
}
157147

158148
public void start(HistoryEvent startWorkflowEvent) {
159149
workflow.start(startWorkflowEvent, context);
160150
}
151+
152+
public WorkflowExecutionException mapUnexpectedException(Throwable exception) {
153+
return workflow.mapUnexpectedException(exception);
154+
}
161155
}

src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
package io.temporal.internal.replay;
2121

22+
import static io.temporal.internal.common.CheckedExceptionWrapper.wrap;
2223
import static io.temporal.internal.common.ProtobufTimeUtils.toJavaDuration;
23-
import static io.temporal.worker.WorkflowErrorPolicy.FailWorkflow;
2424

2525
import com.google.common.base.Throwables;
2626
import com.google.protobuf.util.Durations;
@@ -184,16 +184,23 @@ private void handleWorkflowTaskImpl(PollWorkflowTaskQueueResponseOrBuilder workf
184184
}
185185
}
186186
} catch (Throwable e) {
187+
// Fail workflow if exception is of the specified type
187188
WorkflowImplementationOptions implementationOptions =
188189
this.replayWorkflowExecutor.getWorkflowImplementationOptions();
189-
if (implementationOptions.getWorkflowErrorPolicy() == FailWorkflow) {
190-
// fail workflow
191-
throw replayWorkflowExecutor.mapError(e);
192-
} else {
193-
metricsScope.counter(MetricsType.WORKFLOW_TASK_NO_COMPLETION_COUNTER).inc(1);
194-
// fail workflow task, not a workflow
195-
throw e;
190+
Class<? extends Throwable>[] failTypes =
191+
implementationOptions.getFailWorkflowExceptionTypes();
192+
for (Class<? extends Throwable> failType : failTypes) {
193+
if (failType.isAssignableFrom(e.getClass())) {
194+
// Wrap any failure into InternalWorkflowTaskException to support specifying them
195+
// in the implementation options.
196+
if (!(e instanceof InternalWorkflowTaskException)) {
197+
e = new InternalWorkflowTaskException(e);
198+
}
199+
throw replayWorkflowExecutor.mapUnexpectedException(e);
200+
}
196201
}
202+
metricsScope.counter(MetricsType.WORKFLOW_TASK_NO_COMPLETION_COUNTER).inc(1);
203+
throw wrap(e);
197204
} finally {
198205
if (!timerStopped) {
199206
sw.stop();

src/main/java/io/temporal/internal/replay/ReplayWorkflowTaskHandler.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,6 @@ public WorkflowTaskHandler.Result handleWorkflowTask(PollWorkflowTaskQueueRespon
120120
private Result failureToResult(PollWorkflowTaskQueueResponse workflowTask, Throwable e)
121121
throws Exception {
122122
String workflowType = workflowTask.getWorkflowType().getName();
123-
// Fail workflow and not a task as WorkflowExecutionException is thrown only if FailWorkflow
124-
// policy was set.
125123
if (e instanceof WorkflowExecutionException) {
126124
RespondWorkflowTaskCompletedRequest response =
127125
RespondWorkflowTaskCompletedRequest.newBuilder()

src/main/java/io/temporal/internal/statemachines/WorkflowStateMachines.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package io.temporal.internal.statemachines;
2121

22+
import static io.temporal.internal.common.CheckedExceptionWrapper.unwrap;
2223
import static io.temporal.internal.common.WorkflowExecutionUtils.getEventTypeForCommand;
2324
import static io.temporal.internal.common.WorkflowExecutionUtils.isCommandEvent;
2425
import static io.temporal.internal.statemachines.LocalActivityStateMachine.LOCAL_ACTIVITY_MARKER_NAME;
@@ -50,7 +51,7 @@
5051
import io.temporal.failure.CanceledFailure;
5152
import io.temporal.internal.replay.ExecuteActivityParameters;
5253
import io.temporal.internal.replay.ExecuteLocalActivityParameters;
53-
import io.temporal.internal.replay.NonDeterministicWorkflowError;
54+
import io.temporal.internal.replay.InternalWorkflowTaskException;
5455
import io.temporal.internal.replay.StartChildWorkflowExecutionParameters;
5556
import io.temporal.internal.worker.ActivityTaskHandler;
5657
import io.temporal.workflow.ChildWorkflowCancellationType;
@@ -176,7 +177,7 @@ public final void handleEvent(HistoryEvent event, boolean hasNextEvent) {
176177
try {
177178
handleEventImpl(event, hasNextEvent);
178179
} catch (RuntimeException e) {
179-
throw new NonDeterministicWorkflowError(
180+
throw new InternalWorkflowTaskException(
180181
"Failure handling event "
181182
+ event.getEventId()
182183
+ " of '"
@@ -189,7 +190,7 @@ public final void handleEvent(HistoryEvent event, boolean hasNextEvent) {
189190
+ this.workflowTaskStartedEventId
190191
+ ", Currently Processing StartedEventId="
191192
+ this.currentStartedEventId,
192-
e);
193+
unwrap(e));
193194
}
194195
}
195196

src/main/java/io/temporal/internal/sync/DeterministicRunner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static DeterministicRunner newRunner(
7575
*
7676
* @throws Throwable if one of the threads didn't handle an exception.
7777
*/
78-
void runUntilAllBlocked() throws Throwable;
78+
void runUntilAllBlocked();
7979

8080
/** IsDone returns true when all of threads are completed */
8181
boolean isDone();

src/main/java/io/temporal/internal/sync/DeterministicRunnerImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ SyncWorkflowContext getWorkflowContext() {
219219
}
220220

221221
@Override
222-
public void runUntilAllBlocked() throws Throwable {
222+
public void runUntilAllBlocked() {
223223
if (rootWorkflowThread == null) {
224224
// TODO: workflow instance specific thread name
225225
rootWorkflowThread =
@@ -291,7 +291,7 @@ public void runUntilAllBlocked() throws Throwable {
291291
}
292292
if (unhandledException != null) {
293293
close();
294-
throw unhandledException;
294+
throw WorkflowInternal.wrap(unhandledException);
295295
}
296296
for (WorkflowThread c : threadsToAdd) {
297297
threads.add(c);

src/main/java/io/temporal/internal/sync/POJOWorkflowImplementationFactory.java

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
package io.temporal.internal.sync;
2121

22-
import static io.temporal.worker.WorkflowErrorPolicy.FailWorkflow;
22+
import static io.temporal.internal.common.CheckedExceptionWrapper.wrap;
23+
import static io.temporal.internal.sync.WorkflowInternal.unwrap;
2324

2425
import com.google.common.base.Preconditions;
2526
import io.temporal.api.common.v1.Payloads;
@@ -102,7 +103,9 @@ void addWorkflowImplementationTypes(
102103

103104
<R> void addWorkflowImplementationFactory(Class<R> clazz, Functions.Func<R> factory) {
104105
WorkflowImplementationOptions unitTestingOptions =
105-
WorkflowImplementationOptions.newBuilder().setWorkflowErrorPolicy(FailWorkflow).build();
106+
WorkflowImplementationOptions.newBuilder()
107+
.setFailWorkflowExceptionTypes(Throwable.class)
108+
.build();
106109
addWorkflowImplementationFactory(unitTestingOptions, clazz, factory);
107110
}
108111

@@ -287,29 +290,47 @@ public Object execute(Object[] arguments) {
287290
} catch (IllegalAccessException e) {
288291
throw new Error(mapToWorkflowExecutionException(e, dataConverter));
289292
} catch (InvocationTargetException e) {
290-
Throwable targetException = e.getTargetException();
291-
if (targetException instanceof Error) {
292-
throw (Error) targetException;
293+
Throwable target = e.getTargetException();
294+
if (target instanceof DestroyWorkflowThreadError) {
295+
throw (DestroyWorkflowThreadError) target;
293296
}
294-
if (log.isErrorEnabled()) {
295-
boolean cancelRequested =
296-
WorkflowInternal.getRootWorkflowContext().getContext().isCancelRequested();
297-
if (!cancelRequested || !FailureConverter.isCanceledCause(targetException)) {
298-
log.error(
299-
"Workflow execution failure "
300-
+ "WorkflowId="
301-
+ info.getWorkflowId()
302-
+ ", RunId="
303-
+ info.getRunId()
304-
+ ", WorkflowType="
305-
+ info.getWorkflowType(),
306-
targetException);
297+
Throwable exception = unwrap(target);
298+
299+
WorkflowImplementationOptions options = implementationOptions.get(info.getWorkflowType());
300+
Class<? extends Throwable>[] failTypes = options.getFailWorkflowExceptionTypes();
301+
if (exception instanceof TemporalFailure) {
302+
logWorkflowExecutionException(info, exception);
303+
throw mapToWorkflowExecutionException(exception, dataConverter);
304+
}
305+
for (Class<? extends Throwable> failType : failTypes) {
306+
if (failType.isAssignableFrom(exception.getClass())) {
307+
// fail workflow
308+
if (log.isErrorEnabled()) {
309+
boolean cancelRequested =
310+
WorkflowInternal.getRootWorkflowContext().getContext().isCancelRequested();
311+
if (!cancelRequested || !FailureConverter.isCanceledCause(exception)) {
312+
logWorkflowExecutionException(info, exception);
313+
}
314+
}
315+
throw mapToWorkflowExecutionException(exception, dataConverter);
307316
}
308317
}
309-
throw mapToWorkflowExecutionException(targetException, dataConverter);
318+
throw wrap(exception);
310319
}
311320
}
312321

322+
private void logWorkflowExecutionException(WorkflowInfo info, Throwable exception) {
323+
log.error(
324+
"Workflow execution failure "
325+
+ "WorkflowId="
326+
+ info.getWorkflowId()
327+
+ ", RunId="
328+
+ info.getRunId()
329+
+ ", WorkflowType="
330+
+ info.getWorkflowType(),
331+
exception);
332+
}
333+
313334
@Override
314335
public void init(WorkflowOutboundCallsInterceptor outboundCalls) {
315336
WorkflowInternal.getRootWorkflowContext().setHeadInterceptor(outboundCalls);
@@ -365,11 +386,6 @@ static WorkflowExecutionException mapToWorkflowExecutionException(
365386
return new WorkflowExecutionException(failure);
366387
}
367388

368-
static WorkflowExecutionException mapError(Throwable error) {
369-
Failure failure = FailureConverter.exceptionToFailureNoUnwrapping(error);
370-
return new WorkflowExecutionException(failure);
371-
}
372-
373389
@Override
374390
public String toString() {
375391
return "POJOWorkflowImplementationFactory{"

src/main/java/io/temporal/internal/sync/SyncWorkflow.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public void handleSignal(String signalName, Optional<Payloads> input, long event
130130
}
131131

132132
@Override
133-
public boolean eventLoop() throws Throwable {
133+
public boolean eventLoop() {
134134
if (runner == null) {
135135
return false;
136136
}
@@ -173,9 +173,4 @@ public WorkflowExecutionException mapUnexpectedException(Throwable failure) {
173173
return POJOWorkflowImplementationFactory.mapToWorkflowExecutionException(
174174
failure, dataConverter);
175175
}
176-
177-
@Override
178-
public WorkflowExecutionException mapError(Throwable failure) {
179-
return POJOWorkflowImplementationFactory.mapError(failure);
180-
}
181176
}

0 commit comments

Comments
 (0)