temporalio 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,9 @@ require 'google/protobuf/well_known_types'
4
4
  require 'securerandom'
5
5
  require 'temporalio/activity'
6
6
  require 'temporalio/api'
7
+ require 'temporalio/client/activity_execution'
8
+ require 'temporalio/client/activity_execution_count'
9
+ require 'temporalio/client/activity_handle'
7
10
  require 'temporalio/client/activity_id_reference'
8
11
  require 'temporalio/client/async_activity_handle'
9
12
  require 'temporalio/client/connection'
@@ -27,6 +30,35 @@ module Temporalio
27
30
  module Internal
28
31
  module Client
29
32
  class Implementation < Temporalio::Client::Interceptor::Outbound
33
+ # Proto routing convention for standalone activity completion: `*_by_id` requests carry
34
+ # `resource_id = "activity:<activity_id>"`. See the resource_id field comment on
35
+ # `RecordActivityTaskHeartbeatByIdRequest` (and the analogous Completed/Failed/Canceled
36
+ # requests) in `workflowservice/v1/request_response.proto`. Workflow-scheduled activities
37
+ # leave resource_id empty.
38
+ STANDALONE_ACTIVITY_RESOURCE_ID_PREFIX = 'activity:'
39
+
40
+ # Returns the activity_id / workflow_id / run_id / resource_id fields for a `*_by_id`
41
+ # async-completion request, depending on whether the reference is the standalone or
42
+ # workflow-bound form. Splat into the request constructor's kwargs alongside the
43
+ # request-specific fields.
44
+ def self._activity_id_reference_request_fields(ref)
45
+ if ref.standalone?
46
+ {
47
+ workflow_id: nil,
48
+ run_id: ref.activity_run_id,
49
+ activity_id: ref.activity_id,
50
+ resource_id: "#{STANDALONE_ACTIVITY_RESOURCE_ID_PREFIX}#{ref.activity_id}"
51
+ }
52
+ else
53
+ {
54
+ workflow_id: ref.workflow_id,
55
+ run_id: ref.run_id,
56
+ activity_id: ref.activity_id,
57
+ resource_id: nil
58
+ }
59
+ end
60
+ end
61
+
30
62
  def self.with_default_rpc_options(user_rpc_options)
31
63
  # If the user did not provide an override_retry, we need to make sure
32
64
  # we use an option set that has it as "true"
@@ -185,7 +217,7 @@ module Temporalio
185
217
  end
186
218
 
187
219
  # If the user wants to wait until completed, we must poll until outcome if not already there
188
- if input.wait_for_stage == Temporalio::Client::WorkflowUpdateWaitStage::COMPLETED && update_resp.outcome
220
+ if input.wait_for_stage == Temporalio::Client::WorkflowUpdateWaitStage::COMPLETED && !update_resp.outcome
189
221
  update_resp.outcome = @client._impl.poll_workflow_update(
190
222
  Temporalio::Client::Interceptor::PollWorkflowUpdateInput.new(
191
223
  workflow_id: start_options.id,
@@ -327,7 +359,8 @@ module Temporalio
327
359
  user_metadata: ProtoUtils.to_user_metadata(
328
360
  start_options.static_summary, start_options.static_details, @client.data_converter
329
361
  ),
330
- header: ProtoUtils.headers_to_proto(start_options.headers, @client.data_converter)
362
+ header: ProtoUtils.headers_to_proto(start_options.headers, @client.data_converter),
363
+ priority: start_options.priority._to_proto
331
364
  )
332
365
  end
333
366
 
@@ -807,12 +840,11 @@ module Temporalio
807
840
  end
808
841
 
809
842
  def heartbeat_async_activity(input)
810
- resp = if input.task_token_or_id_reference.is_a?(Temporalio::Client::ActivityIDReference)
843
+ ref = input.task_token_or_id_reference
844
+ resp = if ref.is_a?(Temporalio::Client::ActivityIDReference)
811
845
  @client.workflow_service.record_activity_task_heartbeat_by_id(
812
846
  Api::WorkflowService::V1::RecordActivityTaskHeartbeatByIdRequest.new(
813
- workflow_id: input.task_token_or_id_reference.workflow_id,
814
- run_id: input.task_token_or_id_reference.run_id,
815
- activity_id: input.task_token_or_id_reference.activity_id,
847
+ **Implementation._activity_id_reference_request_fields(ref),
816
848
  namespace: @client.namespace,
817
849
  identity: @client.connection.identity,
818
850
  details: @client.data_converter.to_payloads(input.details, hints: input.detail_hints)
@@ -822,7 +854,7 @@ module Temporalio
822
854
  else
823
855
  @client.workflow_service.record_activity_task_heartbeat(
824
856
  Api::WorkflowService::V1::RecordActivityTaskHeartbeatRequest.new(
825
- task_token: input.task_token_or_id_reference,
857
+ task_token: ref,
826
858
  namespace: @client.namespace,
827
859
  identity: @client.connection.identity,
828
860
  details: @client.data_converter.to_payloads(input.details, hints: input.detail_hints)
@@ -840,12 +872,11 @@ module Temporalio
840
872
  end
841
873
 
842
874
  def complete_async_activity(input)
843
- if input.task_token_or_id_reference.is_a?(Temporalio::Client::ActivityIDReference)
875
+ ref = input.task_token_or_id_reference
876
+ if ref.is_a?(Temporalio::Client::ActivityIDReference)
844
877
  @client.workflow_service.respond_activity_task_completed_by_id(
845
878
  Api::WorkflowService::V1::RespondActivityTaskCompletedByIdRequest.new(
846
- workflow_id: input.task_token_or_id_reference.workflow_id,
847
- run_id: input.task_token_or_id_reference.run_id,
848
- activity_id: input.task_token_or_id_reference.activity_id,
879
+ **Implementation._activity_id_reference_request_fields(ref),
849
880
  namespace: @client.namespace,
850
881
  identity: @client.connection.identity,
851
882
  result: @client.data_converter.to_payloads([input.result], hints: Array(input.result_hint))
@@ -855,7 +886,7 @@ module Temporalio
855
886
  else
856
887
  @client.workflow_service.respond_activity_task_completed(
857
888
  Api::WorkflowService::V1::RespondActivityTaskCompletedRequest.new(
858
- task_token: input.task_token_or_id_reference,
889
+ task_token: ref,
859
890
  namespace: @client.namespace,
860
891
  identity: @client.connection.identity,
861
892
  result: @client.data_converter.to_payloads([input.result], hints: Array(input.result_hint))
@@ -875,12 +906,11 @@ module Temporalio
875
906
  hints: input.last_heartbeat_detail_hints
876
907
  )
877
908
  end
878
- if input.task_token_or_id_reference.is_a?(Temporalio::Client::ActivityIDReference)
909
+ ref = input.task_token_or_id_reference
910
+ if ref.is_a?(Temporalio::Client::ActivityIDReference)
879
911
  @client.workflow_service.respond_activity_task_failed_by_id(
880
912
  Api::WorkflowService::V1::RespondActivityTaskFailedByIdRequest.new(
881
- workflow_id: input.task_token_or_id_reference.workflow_id,
882
- run_id: input.task_token_or_id_reference.run_id,
883
- activity_id: input.task_token_or_id_reference.activity_id,
913
+ **Implementation._activity_id_reference_request_fields(ref),
884
914
  namespace: @client.namespace,
885
915
  identity: @client.connection.identity,
886
916
  failure: @client.data_converter.to_failure(input.error),
@@ -891,7 +921,7 @@ module Temporalio
891
921
  else
892
922
  @client.workflow_service.respond_activity_task_failed(
893
923
  Api::WorkflowService::V1::RespondActivityTaskFailedRequest.new(
894
- task_token: input.task_token_or_id_reference,
924
+ task_token: ref,
895
925
  namespace: @client.namespace,
896
926
  identity: @client.connection.identity,
897
927
  failure: @client.data_converter.to_failure(input.error),
@@ -904,12 +934,11 @@ module Temporalio
904
934
  end
905
935
 
906
936
  def report_cancellation_async_activity(input)
907
- if input.task_token_or_id_reference.is_a?(Temporalio::Client::ActivityIDReference)
937
+ ref = input.task_token_or_id_reference
938
+ if ref.is_a?(Temporalio::Client::ActivityIDReference)
908
939
  @client.workflow_service.respond_activity_task_canceled_by_id(
909
940
  Api::WorkflowService::V1::RespondActivityTaskCanceledByIdRequest.new(
910
- workflow_id: input.task_token_or_id_reference.workflow_id,
911
- run_id: input.task_token_or_id_reference.run_id,
912
- activity_id: input.task_token_or_id_reference.activity_id,
941
+ **Implementation._activity_id_reference_request_fields(ref),
913
942
  namespace: @client.namespace,
914
943
  identity: @client.connection.identity,
915
944
  details: @client.data_converter.to_payloads(input.details, hints: input.detail_hints)
@@ -919,7 +948,7 @@ module Temporalio
919
948
  else
920
949
  @client.workflow_service.respond_activity_task_canceled(
921
950
  Api::WorkflowService::V1::RespondActivityTaskCanceledRequest.new(
922
- task_token: input.task_token_or_id_reference,
951
+ task_token: ref,
923
952
  namespace: @client.namespace,
924
953
  identity: @client.connection.identity,
925
954
  details: @client.data_converter.to_payloads(input.details, hints: input.detail_hints)
@@ -929,6 +958,168 @@ module Temporalio
929
958
  end
930
959
  nil
931
960
  end
961
+
962
+ def start_activity(input)
963
+ raise ArgumentError, 'activity_id is required' if input.activity_id.nil? || input.activity_id.empty?
964
+ raise ArgumentError, 'task_queue is required' if input.task_queue.nil? || input.task_queue.to_s.empty?
965
+ if input.schedule_to_close_timeout.nil? && input.start_to_close_timeout.nil?
966
+ raise ArgumentError, 'either schedule_to_close_timeout or start_to_close_timeout is required'
967
+ end
968
+ raise ArgumentError, 'start_delay must be non-negative' if input.start_delay&.negative?
969
+
970
+ req = Api::WorkflowService::V1::StartActivityExecutionRequest.new(
971
+ namespace: @client.namespace,
972
+ identity: @client.connection.identity,
973
+ request_id: SecureRandom.uuid,
974
+ activity_id: input.activity_id,
975
+ activity_type: Api::Common::V1::ActivityType.new(name: input.activity),
976
+ task_queue: Api::TaskQueue::V1::TaskQueue.new(name: input.task_queue.to_s),
977
+ input: @client.data_converter.to_payloads(input.args, hints: input.arg_hints),
978
+ schedule_to_close_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_close_timeout),
979
+ schedule_to_start_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_start_timeout),
980
+ start_to_close_timeout: ProtoUtils.seconds_to_duration(input.start_to_close_timeout),
981
+ heartbeat_timeout: ProtoUtils.seconds_to_duration(input.heartbeat_timeout),
982
+ id_reuse_policy: input.id_reuse_policy,
983
+ id_conflict_policy: input.id_conflict_policy,
984
+ retry_policy: input.retry_policy&._to_proto,
985
+ search_attributes: input.search_attributes&._to_proto,
986
+ user_metadata: ProtoUtils.to_user_metadata(
987
+ input.static_summary, input.static_details, @client.data_converter
988
+ ),
989
+ header: ProtoUtils.headers_to_proto(input.headers, @client.data_converter),
990
+ priority: input.priority._to_proto,
991
+ start_delay: ProtoUtils.seconds_to_duration(input.start_delay)
992
+ )
993
+
994
+ begin
995
+ resp = @client.workflow_service.start_activity_execution(
996
+ req,
997
+ rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
998
+ )
999
+ rescue Error::RPCError => e
1000
+ if e.code == Error::RPCError::Code::ALREADY_EXISTS && e.grpc_status.details.first
1001
+ details = e.grpc_status.details.first.unpack(
1002
+ Api::ErrorDetails::V1::ActivityExecutionAlreadyStartedFailure
1003
+ )
1004
+ if details
1005
+ raise Error::ActivityAlreadyStartedError.new(
1006
+ activity_id: input.activity_id,
1007
+ activity_type: input.activity,
1008
+ activity_run_id: details.run_id
1009
+ )
1010
+ end
1011
+ end
1012
+ raise
1013
+ end
1014
+
1015
+ Temporalio::Client::ActivityHandle.new(
1016
+ client: @client,
1017
+ id: input.activity_id,
1018
+ run_id: resp.run_id,
1019
+ result_hint: input.result_hint
1020
+ )
1021
+ end
1022
+
1023
+ def describe_activity(input)
1024
+ resp = @client.workflow_service.describe_activity_execution(
1025
+ Api::WorkflowService::V1::DescribeActivityExecutionRequest.new(
1026
+ namespace: @client.namespace,
1027
+ activity_id: input.activity_id,
1028
+ run_id: input.activity_run_id || ''
1029
+ ),
1030
+ rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
1031
+ )
1032
+ Temporalio::Client::ActivityExecution::Description.new(resp, @client.data_converter)
1033
+ end
1034
+
1035
+ def cancel_activity(input)
1036
+ @client.workflow_service.request_cancel_activity_execution(
1037
+ Api::WorkflowService::V1::RequestCancelActivityExecutionRequest.new(
1038
+ namespace: @client.namespace,
1039
+ activity_id: input.activity_id,
1040
+ run_id: input.activity_run_id || '',
1041
+ identity: @client.connection.identity,
1042
+ request_id: SecureRandom.uuid,
1043
+ reason: input.reason
1044
+ ),
1045
+ rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
1046
+ )
1047
+ nil
1048
+ end
1049
+
1050
+ def terminate_activity(input)
1051
+ @client.workflow_service.terminate_activity_execution(
1052
+ Api::WorkflowService::V1::TerminateActivityExecutionRequest.new(
1053
+ namespace: @client.namespace,
1054
+ activity_id: input.activity_id,
1055
+ run_id: input.activity_run_id || '',
1056
+ identity: @client.connection.identity,
1057
+ request_id: SecureRandom.uuid,
1058
+ reason: input.reason
1059
+ ),
1060
+ rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
1061
+ )
1062
+ nil
1063
+ end
1064
+
1065
+ def list_activities(input)
1066
+ Enumerator.new do |yielder|
1067
+ req = Api::WorkflowService::V1::ListActivityExecutionsRequest.new(
1068
+ namespace: @client.namespace,
1069
+ query: input.query || ''
1070
+ )
1071
+ loop do
1072
+ resp = @client.workflow_service.list_activity_executions(
1073
+ req,
1074
+ rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
1075
+ )
1076
+ resp.executions.each do |raw_info|
1077
+ yielder << Temporalio::Client::ActivityExecution.new(raw_info)
1078
+ end
1079
+ break if resp.next_page_token.empty?
1080
+
1081
+ req.next_page_token = resp.next_page_token
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ def count_activities(input)
1087
+ resp = @client.workflow_service.count_activity_executions(
1088
+ Api::WorkflowService::V1::CountActivityExecutionsRequest.new(
1089
+ namespace: @client.namespace,
1090
+ query: input.query || ''
1091
+ ),
1092
+ rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
1093
+ )
1094
+ Temporalio::Client::ActivityExecutionCount.new(
1095
+ resp.count,
1096
+ resp.groups.map do |group|
1097
+ Temporalio::Client::ActivityExecutionCount::AggregationGroup.new(
1098
+ group.count,
1099
+ group.group_values.map { |payload| SearchAttributes._value_from_payload(payload) }
1100
+ )
1101
+ end
1102
+ )
1103
+ end
1104
+
1105
+ # Long-polls PollActivityExecution until the activity reaches a terminal state. Returns the
1106
+ # ActivityExecutionOutcome. The server's long-poll deadline may expire before the activity
1107
+ # completes; in that case PollActivityExecutionResponse comes back with an unpopulated
1108
+ # `outcome` field and the call must be reissued.
1109
+ def fetch_activity_outcome(input)
1110
+ req = Api::WorkflowService::V1::PollActivityExecutionRequest.new(
1111
+ namespace: @client.namespace,
1112
+ activity_id: input.activity_id,
1113
+ run_id: input.activity_run_id || ''
1114
+ )
1115
+ loop do
1116
+ resp = @client.workflow_service.poll_activity_execution(
1117
+ req,
1118
+ rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
1119
+ )
1120
+ return resp.outcome if resp.outcome
1121
+ end
1122
+ end
932
1123
  end
933
1124
  end
934
1125
  end
@@ -168,9 +168,18 @@ module Temporalio
168
168
  end
169
169
 
170
170
  def execute_activity(task_token, defn, start)
171
- # Build info
171
+ # Build info. Standalone activities have some empty fields on the wire; translate
172
+ # empty strings to nil for the user-facing Info fields.
173
+ workflow_id = Internal::ProtoUtils.string_or(start.workflow_execution&.workflow_id, nil)
174
+ workflow_run_id = Internal::ProtoUtils.string_or(start.workflow_execution&.run_id, nil)
175
+ workflow_type = Internal::ProtoUtils.string_or(start.workflow_type, nil)
176
+ activity_run_id = Internal::ProtoUtils.string_or(start.run_id, nil)
177
+ # `namespace` is always set. `workflow_namespace` is the deprecated accessor, nil for standalone activities.
178
+ namespace = Internal::ProtoUtils.string_or(start.workflow_namespace, @worker.options.client.namespace)
179
+ workflow_namespace = workflow_id.nil? ? nil : namespace
172
180
  info = Activity::Info.new(
173
181
  activity_id: start.activity_id,
182
+ activity_run_id: activity_run_id,
174
183
  activity_type: start.activity_type,
175
184
  attempt: start.attempt,
176
185
  current_attempt_scheduled_time: Internal::ProtoUtils.timestamp_to_time(
@@ -178,6 +187,7 @@ module Temporalio
178
187
  ) || raise, # Never nil
179
188
  heartbeat_timeout: Internal::ProtoUtils.duration_to_seconds(start.heartbeat_timeout),
180
189
  local?: start.is_local,
190
+ namespace: namespace,
181
191
  priority: Priority._from_proto(start.priority),
182
192
  raw_heartbeat_details: begin
183
193
  payloads = start.heartbeat_details.to_ary
@@ -192,10 +202,10 @@ module Temporalio
192
202
  started_time: Internal::ProtoUtils.timestamp_to_time(start.started_time) || raise, # Never nil
193
203
  task_queue: @worker.options.task_queue,
194
204
  task_token:,
195
- workflow_id: start.workflow_execution.workflow_id,
196
- workflow_namespace: start.workflow_namespace,
197
- workflow_run_id: start.workflow_execution.run_id,
198
- workflow_type: start.workflow_type
205
+ workflow_id: workflow_id,
206
+ workflow_namespace: workflow_namespace,
207
+ workflow_run_id: workflow_run_id,
208
+ workflow_type: workflow_type
199
209
  )
200
210
 
201
211
  # Build input
@@ -481,15 +481,25 @@ module Temporalio
481
481
  @instance.pending_nexus_operation_starts.delete(seq)
482
482
  end
483
483
 
484
- # Handle start failure
485
- if resolution.failed
484
+ # Handle start resolution
485
+ case resolution.status
486
+ when :operation_token
487
+ operation_token = resolution.operation_token
488
+ when :started_sync
489
+ operation_token = nil
490
+ when :failed
491
+ input.cancellation.remove_cancel_callback(cancel_callback_key)
492
+ raise @instance.failure_converter.from_failure(
493
+ resolution.failed, @instance.payload_converter
494
+ )
495
+ else
486
496
  input.cancellation.remove_cancel_callback(cancel_callback_key)
487
- raise @instance.failure_converter.from_failure(resolution.failed, @instance.payload_converter)
497
+ raise "Unknown Nexus operation start status: #{resolution.status.inspect}"
488
498
  end
489
499
 
490
500
  # Create handle and add to pending operations (result will come via resolve_nexus_operation)
491
501
  handle = NexusOperationHandle.new(
492
- operation_token: resolution.operation_token,
502
+ operation_token:,
493
503
  instance: @instance,
494
504
  cancellation: input.cancellation,
495
505
  cancel_callback_key:,
@@ -18,11 +18,13 @@ module Temporalio
18
18
  def self.default_info
19
19
  @default_info ||= Activity::Info.new(
20
20
  activity_id: 'test',
21
+ activity_run_id: nil,
21
22
  activity_type: 'unknown',
22
23
  attempt: 1,
23
24
  current_attempt_scheduled_time: Time.at(0),
24
25
  heartbeat_timeout: nil,
25
26
  local?: false,
27
+ namespace: 'default',
26
28
  priority: Temporalio::Priority.default,
27
29
  raw_heartbeat_details: [],
28
30
  retry_policy: RetryPolicy.new,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Temporalio
4
- VERSION = '1.4.1'
4
+ VERSION = '1.5.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: temporalio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Temporal Technologies Inc
@@ -138,6 +138,10 @@ files:
138
138
  - lib/temporalio/api/workflowservice/v1/service.rb
139
139
  - lib/temporalio/cancellation.rb
140
140
  - lib/temporalio/client.rb
141
+ - lib/temporalio/client/activity_execution.rb
142
+ - lib/temporalio/client/activity_execution_count.rb
143
+ - lib/temporalio/client/activity_execution_status.rb
144
+ - lib/temporalio/client/activity_handle.rb
141
145
  - lib/temporalio/client/activity_id_reference.rb
142
146
  - lib/temporalio/client/async_activity_handle.rb
143
147
  - lib/temporalio/client/connection.rb
@@ -147,6 +151,7 @@ files:
147
151
  - lib/temporalio/client/connection/test_service.rb
148
152
  - lib/temporalio/client/connection/workflow_service.rb
149
153
  - lib/temporalio/client/interceptor.rb
154
+ - lib/temporalio/client/pending_activity_state.rb
150
155
  - lib/temporalio/client/plugin.rb
151
156
  - lib/temporalio/client/schedule.rb
152
157
  - lib/temporalio/client/schedule_handle.rb