From e3ef9b4ee32180730c572bc473054771fffeddce Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Mon, 2 Dec 2024 10:02:25 -0800 Subject: [PATCH] Standardized update failure exception (#2339) --- .../java/io/temporal/client/WorkflowStub.java | 13 ++++++++--- .../temporal/client/WorkflowUpdateHandle.java | 2 ++ .../CompletedWorkflowUpdateHandleImpl.java | 23 +++++++++++++++++-- .../client/RootWorkflowClientInvoker.java | 13 +++++++---- .../updateTest/DynamicUpdateTest.java | 2 +- .../workflow/updateTest/UpdateInfoTest.java | 3 +-- .../workflow/updateTest/UpdateTest.java | 5 +++- .../updateTest/UpdateWithStartTest.java | 4 ++-- 8 files changed, 49 insertions(+), 16 deletions(-) diff --git a/temporal-sdk/src/main/java/io/temporal/client/WorkflowStub.java b/temporal-sdk/src/main/java/io/temporal/client/WorkflowStub.java index 896c5b9d4..208d1d97d 100644 --- a/temporal-sdk/src/main/java/io/temporal/client/WorkflowStub.java +++ b/temporal-sdk/src/main/java/io/temporal/client/WorkflowStub.java @@ -83,10 +83,11 @@ static WorkflowStub fromTyped(T typed) { * @param type of the update return value * @param args update method arguments * @return update result - * @throws WorkflowNotFoundException if the workflow execution doesn't exist or completed and - * can't be signalled + * @throws WorkflowUpdateException if the update is rejected or failed during it's execution by + * the workflow. + * @throws WorkflowNotFoundException if the workflow execution doesn't exist or is completed. * @throws WorkflowServiceException for all other failures including networking and service - * availability issues + * availability issues. */ @Experimental R update(String updateName, Class resultClass, Object... args); @@ -103,6 +104,9 @@ static WorkflowStub fromTyped(T typed) { * @param type of the update return value * @param args update method arguments * @return update handle that can be used to get the result of the update. + * @throws WorkflowNotFoundException if the workflow execution doesn't exist or completed. + * @throws WorkflowServiceException for all other failures including networking and service + * availability issues. */ @Experimental WorkflowUpdateHandle startUpdate( @@ -116,6 +120,9 @@ WorkflowUpdateHandle startUpdate( * @param options options that will be used to configure and start a new update request. * @param args update method arguments * @return update handle that can be used to get the result of the update. + * @throws WorkflowNotFoundException if the workflow execution doesn't exist or completed. + * @throws WorkflowServiceException for all other failures including networking and service + * availability issues. */ @Experimental WorkflowUpdateHandle startUpdate(UpdateOptions options, Object... args); diff --git a/temporal-sdk/src/main/java/io/temporal/client/WorkflowUpdateHandle.java b/temporal-sdk/src/main/java/io/temporal/client/WorkflowUpdateHandle.java index 525586875..f933f4412 100644 --- a/temporal-sdk/src/main/java/io/temporal/client/WorkflowUpdateHandle.java +++ b/temporal-sdk/src/main/java/io/temporal/client/WorkflowUpdateHandle.java @@ -49,6 +49,7 @@ public interface WorkflowUpdateHandle { * Returns the result of the workflow update. * * @return the result of the workflow update + * @throws WorkflowUpdateException if the update was rejected or failed by the workflow. */ T getResult(); @@ -58,6 +59,7 @@ public interface WorkflowUpdateHandle { * @param timeout maximum time to wait and perform the background long polling * @param unit unit of timeout * @throws WorkflowUpdateTimeoutOrCancelledException if the timeout is reached. + * @throws WorkflowUpdateException if the update was rejected or failed by the workflow. * @return the result of the workflow update */ T getResult(long timeout, TimeUnit unit); diff --git a/temporal-sdk/src/main/java/io/temporal/internal/client/CompletedWorkflowUpdateHandleImpl.java b/temporal-sdk/src/main/java/io/temporal/internal/client/CompletedWorkflowUpdateHandleImpl.java index b8dfadfb0..102341999 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/client/CompletedWorkflowUpdateHandleImpl.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/client/CompletedWorkflowUpdateHandleImpl.java @@ -21,6 +21,7 @@ package io.temporal.internal.client; import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.client.WorkflowUpdateException; import io.temporal.client.WorkflowUpdateHandle; import io.temporal.common.Experimental; import java.util.concurrent.CompletableFuture; @@ -31,12 +32,22 @@ public final class CompletedWorkflowUpdateHandleImpl implements WorkflowUpdat private final String id; private final WorkflowExecution execution; + private final WorkflowUpdateException exception; private final T result; public CompletedWorkflowUpdateHandleImpl(String id, WorkflowExecution execution, T result) { this.id = id; this.execution = execution; this.result = result; + this.exception = null; + } + + public CompletedWorkflowUpdateHandleImpl( + String id, WorkflowExecution execution, WorkflowUpdateException ex) { + this.id = id; + this.execution = execution; + this.exception = ex; + this.result = null; } @Override @@ -51,21 +62,29 @@ public String getId() { @Override public T getResult() { + if (exception != null) { + throw exception; + } return result; } @Override public T getResult(long timeout, TimeUnit unit) { - return result; + return getResult(); } @Override public CompletableFuture getResultAsync() { + if (exception != null) { + CompletableFuture result = new CompletableFuture<>(); + result.completeExceptionally(exception); + return result; + } return CompletableFuture.completedFuture(result); } @Override public CompletableFuture getResultAsync(long timeout, TimeUnit unit) { - return CompletableFuture.completedFuture(result); + return getResultAsync(); } } diff --git a/temporal-sdk/src/main/java/io/temporal/internal/client/RootWorkflowClientInvoker.java b/temporal-sdk/src/main/java/io/temporal/internal/client/RootWorkflowClientInvoker.java index 867799b1e..98399c86e 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/client/RootWorkflowClientInvoker.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/client/RootWorkflowClientInvoker.java @@ -533,12 +533,15 @@ private WorkflowUpdateHandle toUpdateHandle( result.getUpdateRef().getWorkflowExecution(), resultValue); case FAILURE: - throw new WorkflowUpdateException( - result.getUpdateRef().getWorkflowExecution(), + return new CompletedWorkflowUpdateHandleImpl<>( result.getUpdateRef().getUpdateId(), - input.getUpdateName(), - dataConverterWithWorkflowContext.failureToException( - result.getOutcome().getFailure())); + result.getUpdateRef().getWorkflowExecution(), + new WorkflowUpdateException( + result.getUpdateRef().getWorkflowExecution(), + result.getUpdateRef().getUpdateId(), + input.getUpdateName(), + dataConverterWithWorkflowContext.failureToException( + result.getOutcome().getFailure()))); default: throw new RuntimeException( "Received unexpected outcome from update request: " diff --git a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/DynamicUpdateTest.java b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/DynamicUpdateTest.java index 404a68e3a..4efabcb87 100644 --- a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/DynamicUpdateTest.java +++ b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/DynamicUpdateTest.java @@ -72,7 +72,7 @@ public void dynamicUpdate() throws ExecutionException, InterruptedException { WorkflowUpdateException.class, () -> stub.startUpdate("reject", WorkflowUpdateStage.COMPLETED, String.class, "update input") - .getResultAsync()); + .getResult()); stub.startUpdate("complete", WorkflowUpdateStage.COMPLETED, Void.class).getResultAsync().get(); diff --git a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateInfoTest.java b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateInfoTest.java index c10e8e630..069d783ac 100644 --- a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateInfoTest.java +++ b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateInfoTest.java @@ -72,8 +72,7 @@ public void testUpdateInfo() throws ExecutionException, InterruptedException { WorkflowUpdateException.class, () -> stub.startUpdate(updateOptionsBuilder.setUpdateId("reject").build(), 0, "") - .getResultAsync()); - + .getResult()); workflow.complete(); String result = testWorkflowRule diff --git a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateTest.java b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateTest.java index 4dbe7fa9d..575185972 100644 --- a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateTest.java +++ b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateTest.java @@ -199,7 +199,10 @@ public void testUpdateUntyped() throws ExecutionException, InterruptedException // send a bad update that will be rejected through the sync path assertThrows( WorkflowUpdateException.class, - () -> workflowStub.startUpdate("update", ACCEPTED, String.class, 0, "Bad Update")); + () -> + workflowStub + .startUpdate("update", ACCEPTED, String.class, 0, "Bad Update") + .getResult()); workflowStub.update("complete", void.class); diff --git a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateWithStartTest.java b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateWithStartTest.java index 692437d48..c3efeb1de 100644 --- a/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateWithStartTest.java +++ b/temporal-sdk/src/test/java/io/temporal/workflow/updateTest/UpdateWithStartTest.java @@ -478,8 +478,8 @@ public void failWhenUpdatedIsRejected() { .build(); assertThrows( - WorkflowServiceException.class, - () -> WorkflowClient.updateWithStart(workflow::execute, updateOp)); + WorkflowUpdateException.class, + () -> WorkflowClient.updateWithStart(workflow::execute, updateOp).getResult()); } @Test