💥 Standalone Activities for Ruby#443
Conversation
…ds, and new nullability for old fields, and a factory for creating the standalone version. Enums for activity id reuse and conflict policies. SAA-specific errors.
…e polling. Add test_async_completion_heartbeat_and_fail_standalone in addition to previous heartbeat test. Don’t use queue mechanism for gathering id info when it can be gotten directly.
Consistency of parameter names and ordering. Add static_details.
…he result exception
Test 2+ activit retries.
Use constant for “activity:” prefix.
chris-olszewski
left a comment
There was a problem hiding this comment.
Agree with all of @maciejdudko's suggestions, but this seems to be in a good state.
Co-authored-by: Maciej Dudkowski <maciej.dudkowski@temporal.io>
…emporalio/sdk-ruby into gmt/ruby-standalone-activities
Co-authored-by: Maciej Dudkowski <maciej.dudkowski@temporal.io>
…emporalio/sdk-ruby into gmt/ruby-standalone-activities
Co-authored-by: Maciej Dudkowski <maciej.dudkowski@temporal.io>
…emporalio/sdk-ruby into gmt/ruby-standalone-activities
Co-authored-by: Maciej Dudkowski <maciej.dudkowski@temporal.io>
|
I turned on strict Steep checking temporarily when adding the protobuf types so I wound up adding more entries -- e325de4 @chris-olszewski |
maciejdudko
left a comment
There was a problem hiding this comment.
Just a few minor corrections needed.
| class Client | ||
| # More detailed breakdown of a running activity's state. | ||
| module PendingActivityState | ||
| UNSPECIFIED = Api::Enums::V1::PendingActivityState::PENDING_ACTIVITY_STATE_UNSPECIFIED |
There was a problem hiding this comment.
Since we're using zero_means_nil, we shouldn't define UNSPECIFIED here.
| workflow_run_id = Internal::ProtoUtils.string_or(start.workflow_execution&.run_id, nil) | ||
| workflow_type = Internal::ProtoUtils.string_or(start.workflow_type, nil) | ||
| activity_run_id = Internal::ProtoUtils.string_or(start.run_id, nil) | ||
| # `namespace` is always set (falling back to the client's namespace for standalone activities). |
There was a problem hiding this comment.
The comment is incorrect. The server will set workflow_namespace for SAA too (explained in API docs). The fallback is just in case of a server bug or something.
| # Controls behavior when an activity with the same ID was previously run and is now closed. | ||
| # Defaults to `ALLOW_DUPLICATE`. |
There was a problem hiding this comment.
Nit: technically this is just enum declaration, so there's no default to speak of. The second sentence can be removed (same for ActivityIDReusePolicy below.)
| attr_reader current_attempt_scheduled_time: Time | ||
| attr_reader heartbeat_timeout: Float? | ||
| attr_reader local?: bool | ||
| attr_reader namespace: String? |
There was a problem hiding this comment.
namespace is not optional.
| # @see https://docs.temporal.io/activities | ||
| module ActivityIDConflictPolicy | ||
| # Unset. | ||
| UNSPECIFIED = Api::Enums::V1::ActivityIdConflictPolicy::ACTIVITY_ID_CONFLICT_POLICY_UNSPECIFIED |
There was a problem hiding this comment.
This is an input-only enum and the default is FAIL, so we don't need UNSPECIFIED defined here.
Add standalone activity API (
Client#start_activity+ friends)Introduces support for standalone activities — activities that execute independently of any workflow.
💥 Breaking Changes:
Activity::Info#workflow_id,#workflow_run_id,#workflow_typeare now nullable (String?); they returnnilwhen the activity is standalone.Activity::Info#workflow_namespaceis now marked@deprecatedin favor of the new#namespaceaccessor. Both fields always carry the same value regardless of standalone-vs-workflow context; the rename matches the cross-SDK convention.New public API surface:
Client#start_activity/Client#execute_activity— start a standalone activity execution by name, class, instance, orActivity::Definition::Info;execute_activityisstart_activity+handle.resultas a shortcutClient#activity_handle(activity_id, activity_run_id:, result_hint:)— get a handle to an existing standalone activity (e.g. one started by a different process)Client#list_activities(query)/Client#count_activities(query)— visibility queries over standalone activities;list_activitiesreturns anEnumerator<ActivityExecution>Client::ActivityHandle— handle returned bystart_activity/activity_handle; provides#result(result_hint:, rpc_options:),#describe,#cancel(reason),#terminate(reason)Client::ActivityExecution— lightweight metadata used inlist_activitiesresultsClient::ActivityExecution::Description— rich descriptor returned byActivityHandle#describe; subclass ofActivityExecution. Exposes ~25 fields from the proto (status,attempt,retry_policy,last_failure,last_heartbeat_time,priority,canceled_reason,static_summary/static_details, etc.)Client::ActivityExecutionCount+::AggregationGroup— result ofcount_activities(), with group-by supportClient::ActivityExecutionStatus— enum module wrapping the proto's status valuesActivityIDReusePolicy+ActivityIDConflictPolicy— enum modules inlib/temporalio/common_enums.rbmirroring the workflow-side policiesError::ActivityAlreadyStartedError(inerror/failure.rb) — raised bystart_activitywhen the server rejects a duplicate IDError::ActivityFailedError(inerror.rb) — raised byhandle.resultwhen the activity terminates in failure;causecarries the underlying failure (ApplicationError,CanceledError,TimeoutError,TerminatedError)New interceptor API:
Client::Interceptor::StartActivityInput,DescribeActivityInput,CancelActivityInput,TerminateActivityInput,ListActivitiesInput,CountActivitiesInput,FetchActivityOutcomeInput— seven new Data.define input typesClient::Interceptor::Outboundextended with seven matching pass-through methods (start_activity,describe_activity,cancel_activity,terminate_activity,list_activities,count_activities,fetch_activity_outcome)Activity::Infoadditions:#activity_run_id— run-scoped id assigned by the server to each standalone activity execution (nilfor workflow-dispatched)#namespace— canonical namespace accessor (preferred over the deprecated#workflow_namespace)#in_workflow?— predicate distinguishing workflow-dispatched activities from standalone onesClient#async_activity_handleextension (no new method):ActivityIDReferenceconstructed viaActivityIDReference.for_standalone(activity_id:, activity_run_id:).ActivityIDReferenceadditions:ActivityIDReference.for_standalone(activity_id:, activity_run_id: nil)— class factory for the new standalone shape.#standalone?— predicate for branching in async-completion routingCI / dev server:
frontend.activityAPIsEnabled,activity.enableStandalone,history.enableChasm,history.enableTransitionHistory(added totest/test.rb).sdk-coresubmodule toa22517e4(Rust crate 0.4.0) to pick up the worker-sidecoresdk.activity_task.Start.run_idproto field used to plumbactivity_run_idintoActivity::Info.Tests:
test/client_activity_test.rb(28 tests) — integration tests covering the full lifecycle: start, execute, by-name, already-started, timeouts, failure-throws-ActivityFailedError, describe (running / terminated / canceled / retried / raw-info parity), state-transition-count, terminate-result-throws, polling-correctness (with timing assertions across one server long-poll deadline boundary), list, count, get-handle-with-nil-run-id, cancel-transitions-to-CANCELED, retry-policy / priority / id-reuse / id-conflict-policy round tripstest/client_activity_async_completion_test.rb(6 tests) — async completion viaAsyncActivityHandleagainst a standaloneActivityIDReference: complete by activity_id, complete with run_id, heartbeat, fail, heartbeat+fail, report_cancellationtest/client_activity_hints_test.rb(6 tests) — definition hints used for client-side arg encode and worker-side decode, definitionresult_hintused for worker-side encode and client-side decode, call-site overrides onstart_activity, by-name activities produce nil hints,handle.result(result_hint:)overrides,activity_handle(id, result_hint:)constructor-time hint,AsyncActivityHandle#complete(result, result_hint:)propagates the hinttest/client_activity_interceptor_chain_test.rb(5 tests) — multi-interceptor ordering for activity client calls; verifies first-added-is-outermosttest/client_workflow_interceptor_chain_test.rb(2 tests) — mirror of the above for the workflow client chaintest/activity_info_standalone_test.rb(2 tests) —Activity::Infostandalone-vs-workflow fields asserted from inside the activity bodytest/client/activity_id_reference_test.rb(5 tests) — factory shapes,for_standalonecorrectness, both forms coexist independentlyDocumentation:
README.md— added "Standalone Activities" subsection under "Activities" with five runnable code examples (start, execute, get-handle + describe/result/cancel/terminate, list/count, in-activity-body context introspection)