diff --git a/CHANGELOG.md b/CHANGELOG.md index 599bc656..57b19d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,9 +30,19 @@ async completion. See https://docs.temporal.io/standalone-activity for the cross-SDK feature overview. +### Fixed + +#### `execute_update_with_start_workflow` no longer raises `RPCError NOT_FOUND` on validator rejection + +When a `workflow_update_validator` rejected an update sent via +`Client#execute_update_with_start_workflow` (or `#start_update_with_start_workflow` with +`wait_for_stage: COMPLETED`), the client polled history for an outcome that was never written +and surfaced the failure as `Temporalio::Error::RPCError` with code `NOT_FOUND`. The caller now +correctly receives `Temporalio::Error::WorkflowUpdateFailedError`. (#454) + #### Start Delay for Standalone Activities `Client#start_activity` and `Client#execute_activity` now accept a `start_delay:` kwarg. When set, the server creates the activity immediately, but defers dispatch to a worker until the delay elapses. Retry attempts do not re-apply the delay. `ScheduleToStart` and `ScheduleToClose` timeout clocks begin counting after the delay -elapses; `StartToClose` and `Heartbeat` are unaffected. Currently experimental. \ No newline at end of file +elapses; `StartToClose` and `Heartbeat` are unaffected. Currently experimental. diff --git a/temporalio/lib/temporalio/internal/client/implementation.rb b/temporalio/lib/temporalio/internal/client/implementation.rb index 9406b73c..1cc7bb67 100644 --- a/temporalio/lib/temporalio/internal/client/implementation.rb +++ b/temporalio/lib/temporalio/internal/client/implementation.rb @@ -217,7 +217,7 @@ def start_update_with_start_workflow(input) end # If the user wants to wait until completed, we must poll until outcome if not already there - if input.wait_for_stage == Temporalio::Client::WorkflowUpdateWaitStage::COMPLETED && update_resp.outcome + if input.wait_for_stage == Temporalio::Client::WorkflowUpdateWaitStage::COMPLETED && !update_resp.outcome update_resp.outcome = @client._impl.poll_workflow_update( Temporalio::Client::Interceptor::PollWorkflowUpdateInput.new( workflow_id: start_options.id, diff --git a/temporalio/test/worker_workflow_handler_test.rb b/temporalio/test/worker_workflow_handler_test.rb index b07a1dff..634ac760 100644 --- a/temporalio/test/worker_workflow_handler_test.rb +++ b/temporalio/test/worker_workflow_handler_test.rb @@ -691,6 +691,16 @@ def fail raise Temporalio::Error::ApplicationError, 'Intentional failure' end + workflow_update + def rejected_by_validator(value) + raise "Update handler should not run when validator rejects (got #{value})" + end + + workflow_update_validator :rejected_by_validator + def validate_rejected_by_validator(value) + raise 'Validator rejection reason' if value.negative? + end + workflow_update def start_waiting Temporalio::Workflow.wait_condition { @finish_waiting } @@ -784,6 +794,30 @@ def test_update_with_start_update_failure end end + def test_update_with_start_validator_rejection + worker = Temporalio::Worker.new( + client: env.client, + task_queue: "tq-#{SecureRandom.uuid}", + workflows: [UpdateWithStartWorkflow] + ) + worker.run do + id = "wf-#{SecureRandom.uuid}" + start_workflow_operation = Temporalio::Client::WithStartWorkflowOperation.new( + UpdateWithStartWorkflow, 123, + id:, task_queue: worker.task_queue, id_conflict_policy: Temporalio::WorkflowIDConflictPolicy::FAIL + ) + err = assert_raises(Temporalio::Error::WorkflowUpdateFailedError) do + env.client.execute_update_with_start_workflow( + UpdateWithStartWorkflow.rejected_by_validator, -1, + start_workflow_operation: + ) + end + assert_instance_of Temporalio::Error::ApplicationError, err.cause + assert_equal 'Validator rejection reason', err.cause.message + assert_equal 123, start_workflow_operation.workflow_handle.query(UpdateWithStartWorkflow.counter) + end + end + def test_update_with_start_cancel # Run worker worker = Temporalio::Worker.new(