amit-temporalio 0.3.1

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.
Files changed (179) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +2 -0
  3. data/Cargo.lock +4325 -0
  4. data/Cargo.toml +25 -0
  5. data/Gemfile +23 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1148 -0
  8. data/Rakefile +101 -0
  9. data/ext/Cargo.toml +27 -0
  10. data/lib/temporalio/activity/complete_async_error.rb +11 -0
  11. data/lib/temporalio/activity/context.rb +116 -0
  12. data/lib/temporalio/activity/definition.rb +189 -0
  13. data/lib/temporalio/activity/info.rb +64 -0
  14. data/lib/temporalio/activity.rb +12 -0
  15. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  16. data/lib/temporalio/api/batch/v1/message.rb +31 -0
  17. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  18. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +126 -0
  19. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
  20. data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
  21. data/lib/temporalio/api/cloud/identity/v1/message.rb +41 -0
  22. data/lib/temporalio/api/cloud/namespace/v1/message.rb +42 -0
  23. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  24. data/lib/temporalio/api/cloud/operation/v1/message.rb +28 -0
  25. data/lib/temporalio/api/cloud/region/v1/message.rb +24 -0
  26. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  27. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  28. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  29. data/lib/temporalio/api/command/v1/message.rb +46 -0
  30. data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
  31. data/lib/temporalio/api/common/v1/message.rb +47 -0
  32. data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
  33. data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
  34. data/lib/temporalio/api/enums/v1/common.rb +26 -0
  35. data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
  36. data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
  37. data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
  38. data/lib/temporalio/api/enums/v1/query.rb +22 -0
  39. data/lib/temporalio/api/enums/v1/reset.rb +23 -0
  40. data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
  41. data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
  42. data/lib/temporalio/api/enums/v1/update.rb +22 -0
  43. data/lib/temporalio/api/enums/v1/workflow.rb +30 -0
  44. data/lib/temporalio/api/errordetails/v1/message.rb +42 -0
  45. data/lib/temporalio/api/export/v1/message.rb +24 -0
  46. data/lib/temporalio/api/failure/v1/message.rb +35 -0
  47. data/lib/temporalio/api/filter/v1/message.rb +27 -0
  48. data/lib/temporalio/api/history/v1/message.rb +90 -0
  49. data/lib/temporalio/api/namespace/v1/message.rb +31 -0
  50. data/lib/temporalio/api/nexus/v1/message.rb +40 -0
  51. data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
  52. data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
  53. data/lib/temporalio/api/operatorservice.rb +3 -0
  54. data/lib/temporalio/api/payload_visitor.rb +1513 -0
  55. data/lib/temporalio/api/protocol/v1/message.rb +23 -0
  56. data/lib/temporalio/api/query/v1/message.rb +27 -0
  57. data/lib/temporalio/api/replication/v1/message.rb +26 -0
  58. data/lib/temporalio/api/schedule/v1/message.rb +43 -0
  59. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +25 -0
  60. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +21 -0
  61. data/lib/temporalio/api/sdk/v1/user_metadata.rb +23 -0
  62. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +23 -0
  63. data/lib/temporalio/api/taskqueue/v1/message.rb +45 -0
  64. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  65. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  66. data/lib/temporalio/api/update/v1/message.rb +33 -0
  67. data/lib/temporalio/api/version/v1/message.rb +26 -0
  68. data/lib/temporalio/api/workflow/v1/message.rb +43 -0
  69. data/lib/temporalio/api/workflowservice/v1/request_response.rb +204 -0
  70. data/lib/temporalio/api/workflowservice/v1/service.rb +23 -0
  71. data/lib/temporalio/api/workflowservice.rb +3 -0
  72. data/lib/temporalio/api.rb +14 -0
  73. data/lib/temporalio/cancellation.rb +170 -0
  74. data/lib/temporalio/client/activity_id_reference.rb +32 -0
  75. data/lib/temporalio/client/async_activity_handle.rb +85 -0
  76. data/lib/temporalio/client/connection/cloud_service.rb +726 -0
  77. data/lib/temporalio/client/connection/operator_service.rb +201 -0
  78. data/lib/temporalio/client/connection/service.rb +42 -0
  79. data/lib/temporalio/client/connection/test_service.rb +111 -0
  80. data/lib/temporalio/client/connection/workflow_service.rb +1041 -0
  81. data/lib/temporalio/client/connection.rb +316 -0
  82. data/lib/temporalio/client/interceptor.rb +416 -0
  83. data/lib/temporalio/client/schedule.rb +967 -0
  84. data/lib/temporalio/client/schedule_handle.rb +126 -0
  85. data/lib/temporalio/client/workflow_execution.rb +100 -0
  86. data/lib/temporalio/client/workflow_execution_count.rb +36 -0
  87. data/lib/temporalio/client/workflow_execution_status.rb +18 -0
  88. data/lib/temporalio/client/workflow_handle.rb +389 -0
  89. data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
  90. data/lib/temporalio/client/workflow_update_handle.rb +65 -0
  91. data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
  92. data/lib/temporalio/client.rb +484 -0
  93. data/lib/temporalio/common_enums.rb +41 -0
  94. data/lib/temporalio/converters/data_converter.rb +99 -0
  95. data/lib/temporalio/converters/failure_converter.rb +202 -0
  96. data/lib/temporalio/converters/payload_codec.rb +26 -0
  97. data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
  98. data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
  99. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
  100. data/lib/temporalio/converters/payload_converter/composite.rb +66 -0
  101. data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
  102. data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
  103. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
  104. data/lib/temporalio/converters/payload_converter.rb +71 -0
  105. data/lib/temporalio/converters/raw_value.rb +20 -0
  106. data/lib/temporalio/converters.rb +9 -0
  107. data/lib/temporalio/error/failure.rb +219 -0
  108. data/lib/temporalio/error.rb +155 -0
  109. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
  110. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +31 -0
  111. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
  112. data/lib/temporalio/internal/bridge/api/common/common.rb +26 -0
  113. data/lib/temporalio/internal/bridge/api/core_interface.rb +40 -0
  114. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
  115. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  116. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +56 -0
  117. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +57 -0
  118. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +30 -0
  119. data/lib/temporalio/internal/bridge/api.rb +3 -0
  120. data/lib/temporalio/internal/bridge/client.rb +95 -0
  121. data/lib/temporalio/internal/bridge/runtime.rb +53 -0
  122. data/lib/temporalio/internal/bridge/testing.rb +66 -0
  123. data/lib/temporalio/internal/bridge/worker.rb +85 -0
  124. data/lib/temporalio/internal/bridge.rb +36 -0
  125. data/lib/temporalio/internal/client/implementation.rb +700 -0
  126. data/lib/temporalio/internal/metric.rb +122 -0
  127. data/lib/temporalio/internal/proto_utils.rb +133 -0
  128. data/lib/temporalio/internal/worker/activity_worker.rb +376 -0
  129. data/lib/temporalio/internal/worker/multi_runner.rb +213 -0
  130. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  131. data/lib/temporalio/internal/worker/workflow_instance/context.rb +333 -0
  132. data/lib/temporalio/internal/worker/workflow_instance/details.rb +44 -0
  133. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  134. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  135. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  136. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  137. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  138. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  139. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +415 -0
  140. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  141. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  142. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +163 -0
  143. data/lib/temporalio/internal/worker/workflow_instance.rb +730 -0
  144. data/lib/temporalio/internal/worker/workflow_worker.rb +236 -0
  145. data/lib/temporalio/internal.rb +7 -0
  146. data/lib/temporalio/metric.rb +109 -0
  147. data/lib/temporalio/retry_policy.rb +74 -0
  148. data/lib/temporalio/runtime.rb +314 -0
  149. data/lib/temporalio/scoped_logger.rb +96 -0
  150. data/lib/temporalio/search_attributes.rb +343 -0
  151. data/lib/temporalio/testing/activity_environment.rb +136 -0
  152. data/lib/temporalio/testing/workflow_environment.rb +383 -0
  153. data/lib/temporalio/testing.rb +10 -0
  154. data/lib/temporalio/version.rb +5 -0
  155. data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
  156. data/lib/temporalio/worker/activity_executor/thread_pool.rb +46 -0
  157. data/lib/temporalio/worker/activity_executor.rb +55 -0
  158. data/lib/temporalio/worker/interceptor.rb +362 -0
  159. data/lib/temporalio/worker/thread_pool.rb +237 -0
  160. data/lib/temporalio/worker/tuner.rb +189 -0
  161. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +230 -0
  162. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  163. data/lib/temporalio/worker/workflow_replayer.rb +343 -0
  164. data/lib/temporalio/worker.rb +569 -0
  165. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  166. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  167. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  168. data/lib/temporalio/workflow/definition.rb +566 -0
  169. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  170. data/lib/temporalio/workflow/future.rb +151 -0
  171. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  172. data/lib/temporalio/workflow/info.rb +82 -0
  173. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  174. data/lib/temporalio/workflow/update_info.rb +20 -0
  175. data/lib/temporalio/workflow.rb +529 -0
  176. data/lib/temporalio/workflow_history.rb +47 -0
  177. data/lib/temporalio.rb +11 -0
  178. data/temporalio.gemspec +28 -0
  179. metadata +234 -0
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'temporalio/internal/bridge'
5
+ require 'temporalio/metric'
6
+
7
+ module Temporalio
8
+ module Internal
9
+ class Metric < Temporalio::Metric
10
+ attr_reader :metric_type, :name, :description, :unit, :value_type
11
+
12
+ def initialize(metric_type:, name:, description:, unit:, value_type:, bridge:, bridge_attrs:) # rubocop:disable Lint/MissingSuper
13
+ @metric_type = metric_type
14
+ @name = name
15
+ @description = description
16
+ @unit = unit
17
+ @value_type = value_type
18
+ @bridge = bridge
19
+ @bridge_attrs = bridge_attrs
20
+ end
21
+
22
+ def record(value, additional_attributes: nil)
23
+ bridge_attrs = @bridge_attrs
24
+ bridge_attrs = @bridge_attrs.with_additional(additional_attributes) if additional_attributes
25
+ @bridge.record_value(value, bridge_attrs)
26
+ end
27
+
28
+ def with_additional_attributes(additional_attributes)
29
+ Metric.new(
30
+ metric_type:,
31
+ name:,
32
+ description:,
33
+ unit:,
34
+ value_type:,
35
+ bridge: @bridge,
36
+ bridge_attrs: @bridge_attrs.with_additional(additional_attributes)
37
+ )
38
+ end
39
+
40
+ class Meter < Temporalio::Metric::Meter
41
+ def self.create_from_runtime(runtime)
42
+ bridge = Bridge::Metric::Meter.new(runtime._core_runtime)
43
+ return nil unless bridge
44
+
45
+ Meter.new(bridge, bridge.default_attributes)
46
+ end
47
+
48
+ def initialize(bridge, bridge_attrs) # rubocop:disable Lint/MissingSuper
49
+ @bridge = bridge
50
+ @bridge_attrs = bridge_attrs
51
+ end
52
+
53
+ def create_metric(
54
+ metric_type,
55
+ name,
56
+ description: nil,
57
+ unit: nil,
58
+ value_type: :integer
59
+ )
60
+ Metric.new(
61
+ metric_type:,
62
+ name:,
63
+ description:,
64
+ unit:,
65
+ value_type:,
66
+ bridge: Bridge::Metric.new(@bridge, metric_type, name, description, unit, value_type),
67
+ bridge_attrs: @bridge_attrs
68
+ )
69
+ end
70
+
71
+ def with_additional_attributes(additional_attributes)
72
+ Meter.new(@bridge, @bridge_attrs.with_additional(additional_attributes))
73
+ end
74
+ end
75
+
76
+ class NullMeter < Temporalio::Metric::Meter
77
+ include Singleton
78
+
79
+ def initialize # rubocop:disable Style/RedundantInitialize,Lint/MissingSuper
80
+ end
81
+
82
+ def create_metric(
83
+ metric_type,
84
+ name,
85
+ description: nil,
86
+ unit: nil,
87
+ value_type: :integer
88
+ )
89
+ NullMetric.new(
90
+ metric_type:,
91
+ name:,
92
+ description:,
93
+ unit:,
94
+ value_type:
95
+ )
96
+ end
97
+
98
+ def with_additional_attributes(_additional_attributes)
99
+ self
100
+ end
101
+ end
102
+
103
+ class NullMetric < Temporalio::Metric
104
+ attr_reader :metric_type, :name, :description, :unit, :value_type
105
+
106
+ def initialize(metric_type:, name:, description:, unit:, value_type:) # rubocop:disable Lint/MissingSuper
107
+ @metric_type = metric_type
108
+ @name = name
109
+ @description = description
110
+ @unit = unit
111
+ @value_type = value_type
112
+ end
113
+
114
+ def record(value, additional_attributes: nil); end
115
+
116
+ def with_additional_attributes(_additional_attributes)
117
+ self
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/api'
4
+
5
+ module Temporalio
6
+ module Internal
7
+ module ProtoUtils
8
+ def self.seconds_to_duration(seconds_numeric)
9
+ return nil if seconds_numeric.nil?
10
+
11
+ seconds = seconds_numeric.to_i
12
+ nanos = ((seconds_numeric - seconds) * 1_000_000_000).round
13
+ Google::Protobuf::Duration.new(seconds:, nanos:)
14
+ end
15
+
16
+ def self.duration_to_seconds(duration)
17
+ return nil if duration.nil?
18
+
19
+ # This logic was corrected for timestamp at
20
+ # https://github.com/protocolbuffers/protobuf/pull/2482 but not for
21
+ # duration, so 4.56 is not properly represented in to_f, it becomes
22
+ # 4.5600000000000005.
23
+ (duration.seconds + duration.nanos.quo(1_000_000_000)).to_f
24
+ end
25
+
26
+ def self.time_to_timestamp(time)
27
+ return nil if time.nil?
28
+
29
+ Google::Protobuf::Timestamp.from_time(time)
30
+ end
31
+
32
+ def self.timestamp_to_time(timestamp)
33
+ return nil if timestamp.nil?
34
+
35
+ # The regular to_time on the timestamp converts to local timezone,
36
+ # and we prefer not to make a separate .utc call (converts to local
37
+ # then back to UTC unnecessarily)
38
+ Time.at(timestamp.seconds, timestamp.nanos, :nanosecond, in: 'UTC')
39
+ end
40
+
41
+ def self.memo_to_proto(hash, converter)
42
+ return nil if hash.nil? || hash.empty?
43
+
44
+ Api::Common::V1::Memo.new(fields: memo_to_proto_hash(hash, converter))
45
+ end
46
+
47
+ def self.memo_to_proto_hash(hash, converter)
48
+ return nil if hash.nil? || hash.empty?
49
+
50
+ hash.transform_keys(&:to_s).transform_values { |val| converter.to_payload(val) }
51
+ end
52
+
53
+ def self.memo_from_proto(memo, converter)
54
+ return nil if memo.nil? || memo.fields.size.zero? # rubocop:disable Style/ZeroLengthPredicate Google Maps don't have empty
55
+
56
+ memo.fields.each_with_object({}) { |(key, val), h| h[key] = converter.from_payload(val) } # rubocop:disable Style/HashTransformValues
57
+ end
58
+
59
+ def self.headers_to_proto(headers, converter)
60
+ return nil if headers.nil? || headers.empty?
61
+
62
+ Api::Common::V1::Header.new(fields: headers_to_proto_hash(headers, converter))
63
+ end
64
+
65
+ def self.headers_to_proto_hash(headers, converter)
66
+ return nil if headers.nil? || headers.empty?
67
+
68
+ headers.transform_values { |val| converter.to_payload(val) }
69
+ end
70
+
71
+ def self.headers_from_proto(headers, converter)
72
+ headers_from_proto_map(headers&.fields, converter)
73
+ end
74
+
75
+ def self.headers_from_proto_map(headers, converter)
76
+ return nil if headers.nil? || headers.size.zero? # rubocop:disable Style/ZeroLengthPredicate Google Maps don't have empty
77
+
78
+ headers.each_with_object({}) do |(key, val), h| # rubocop:disable Style/HashTransformValues
79
+ # @type var h: Hash[String, Object?]
80
+ h[key] = converter.from_payload(val)
81
+ end
82
+ end
83
+
84
+ def self.string_or(str, default = nil)
85
+ str && !str.empty? ? str : default
86
+ end
87
+
88
+ def self.enum_to_int(enum_mod, enum_val, zero_means_nil: false)
89
+ # Per https://protobuf.dev/reference/ruby/ruby-generated/#enum when
90
+ # enums are read back, they are symbols if they are known or number
91
+ # otherwise
92
+ enum_val = enum_mod.resolve(enum_val) || raise('Unexpected missing symbol') if enum_val.is_a?(Symbol)
93
+ enum_val = nil if zero_means_nil && enum_val.zero?
94
+ enum_val
95
+ end
96
+
97
+ def self.convert_from_payload_array(converter, payloads)
98
+ return [] if payloads.empty?
99
+
100
+ converter.from_payloads(Api::Common::V1::Payloads.new(payloads:))
101
+ end
102
+
103
+ def self.convert_to_payload_array(converter, values)
104
+ return [] if values.empty?
105
+
106
+ converter.to_payloads(values).payloads.to_ary
107
+ end
108
+
109
+ class LazyMemo
110
+ def initialize(raw_memo, converter)
111
+ @raw_memo = raw_memo
112
+ @converter = converter
113
+ end
114
+
115
+ def get
116
+ @memo = ProtoUtils.memo_from_proto(@raw_memo, @converter) unless defined?(@memo)
117
+ @memo
118
+ end
119
+ end
120
+
121
+ class LazySearchAttributes
122
+ def initialize(raw_search_attributes)
123
+ @raw_search_attributes = raw_search_attributes
124
+ end
125
+
126
+ def get
127
+ @search_attributes = SearchAttributes._from_proto(@raw_search_attributes) unless defined?(@search_attributes)
128
+ @search_attributes
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,376 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/activity'
4
+ require 'temporalio/activity/definition'
5
+ require 'temporalio/cancellation'
6
+ require 'temporalio/converters/raw_value'
7
+ require 'temporalio/internal/bridge/api'
8
+ require 'temporalio/internal/proto_utils'
9
+ require 'temporalio/scoped_logger'
10
+ require 'temporalio/worker/interceptor'
11
+
12
+ module Temporalio
13
+ module Internal
14
+ module Worker
15
+ # Worker for handling activity tasks. Upon overarching worker shutdown, {wait_all_complete} should be used to wait
16
+ # for the activities to complete.
17
+ class ActivityWorker
18
+ LOG_TASKS = false
19
+
20
+ attr_reader :worker, :bridge_worker
21
+
22
+ def initialize(worker:, bridge_worker:)
23
+ @worker = worker
24
+ @bridge_worker = bridge_worker
25
+ @runtime_metric_meter = worker.options.client.connection.options.runtime.metric_meter
26
+
27
+ # Create shared logger that gives scoped activity details
28
+ @scoped_logger = ScopedLogger.new(@worker.options.logger)
29
+ @scoped_logger.scoped_values_getter = proc {
30
+ Activity::Context.current_or_nil&._scoped_logger_info
31
+ }
32
+
33
+ # Build up activity hash by name (can be nil for dynamic), failing if any fail validation
34
+ @activities = worker.options.activities.each_with_object({}) do |act, hash|
35
+ # Class means create each time, instance means just call, definition
36
+ # does nothing special
37
+ defn = Activity::Definition::Info.from_activity(act)
38
+ # Confirm name not in use
39
+ raise ArgumentError, 'Only one dynamic activity allowed' if !defn.name && hash.key?(defn.name)
40
+ raise ArgumentError, "Multiple activities named #{defn.name}" if hash.key?(defn.name)
41
+
42
+ # Confirm executor is a known executor and let it initialize
43
+ executor = worker.options.activity_executors[defn.executor]
44
+ raise ArgumentError, "Unknown executor '#{defn.executor}'" if executor.nil?
45
+
46
+ executor.initialize_activity(defn)
47
+
48
+ hash[defn.name] = defn
49
+ end
50
+
51
+ # Need mutex for the rest of these
52
+ @running_activities_mutex = Mutex.new
53
+ @running_activities = {}
54
+ @running_activities_empty_condvar = ConditionVariable.new
55
+ end
56
+
57
+ def set_running_activity(task_token, activity)
58
+ @running_activities_mutex.synchronize do
59
+ @running_activities[task_token] = activity
60
+ end
61
+ end
62
+
63
+ def get_running_activity(task_token)
64
+ @running_activities_mutex.synchronize do
65
+ @running_activities[task_token]
66
+ end
67
+ end
68
+
69
+ def remove_running_activity(task_token)
70
+ @running_activities_mutex.synchronize do
71
+ @running_activities.delete(task_token)
72
+ @running_activities_empty_condvar.broadcast if @running_activities.empty?
73
+ end
74
+ end
75
+
76
+ def wait_all_complete
77
+ @running_activities_mutex.synchronize do
78
+ @running_activities_empty_condvar.wait(@running_activities_mutex) until @running_activities.empty?
79
+ end
80
+ end
81
+
82
+ def handle_task(task)
83
+ @scoped_logger.debug("Received activity task: #{task}") if LOG_TASKS
84
+ if !task.start.nil?
85
+ handle_start_task(task.task_token, task.start)
86
+ elsif !task.cancel.nil?
87
+ handle_cancel_task(task.task_token, task.cancel)
88
+ else
89
+ raise "Unrecognized activity task: #{task}"
90
+ end
91
+ end
92
+
93
+ def handle_start_task(task_token, start)
94
+ set_running_activity(task_token, nil)
95
+
96
+ # Find activity definition, falling back to dynamic if present
97
+ defn = @activities[start.activity_type] || @activities[nil]
98
+ if defn.nil?
99
+ raise Error::ApplicationError.new(
100
+ "Activity #{start.activity_type} for workflow #{start.workflow_execution.workflow_id} " \
101
+ "is not registered on this worker, available activities: #{@activities.keys.sort.join(', ')}",
102
+ type: 'NotFoundError'
103
+ )
104
+ end
105
+
106
+ # Run everything else in the excecutor
107
+ executor = @worker.options.activity_executors[defn.executor]
108
+ executor.execute_activity(defn) do
109
+ # Set current executor
110
+ Activity::Context._current_executor = executor
111
+ # Execute with error handling
112
+ execute_activity(task_token, defn, start)
113
+ ensure
114
+ # Unset at the end
115
+ Activity::Context._current_executor = nil
116
+ end
117
+ rescue Exception => e # rubocop:disable Lint/RescueException We are intending to catch everything here
118
+ remove_running_activity(task_token)
119
+ @scoped_logger.warn("Failed starting activity #{start.activity_type}")
120
+ @scoped_logger.warn(e)
121
+
122
+ # We need to complete the activity task as failed, but this is on the
123
+ # hot path for polling, so we want to complete it in the background
124
+ begin
125
+ @bridge_worker.complete_activity_task_in_background(
126
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
127
+ task_token:,
128
+ result: Bridge::Api::ActivityResult::ActivityExecutionResult.new(
129
+ failed: Bridge::Api::ActivityResult::Failure.new(
130
+ # TODO(cretz): If failure conversion does slow failure
131
+ # encoding, it can gum up the system
132
+ failure: @worker.options.client.data_converter.to_failure(e)
133
+ )
134
+ )
135
+ )
136
+ )
137
+ rescue StandardError => e_inner
138
+ @scoped_logger.error("Failed building start failure to return for #{start.activity_type}")
139
+ @scoped_logger.error(e_inner)
140
+ end
141
+ end
142
+
143
+ def handle_cancel_task(task_token, cancel)
144
+ activity = get_running_activity(task_token)
145
+ if activity.nil?
146
+ @scoped_logger.warn("Cannot find activity to cancel for token #{task_token}")
147
+ return
148
+ end
149
+ activity._server_requested_cancel = true
150
+ _, cancel_proc = activity.cancellation
151
+ begin
152
+ cancel_proc.call(reason: cancel.reason.to_s)
153
+ rescue StandardError => e
154
+ @scoped_logger.warn("Failed cancelling activity #{activity.info.activity_type} \
155
+ with ID #{activity.info.activity_id}")
156
+ @scoped_logger.warn(e)
157
+ end
158
+ end
159
+
160
+ def execute_activity(task_token, defn, start)
161
+ # Build info
162
+ info = Activity::Info.new(
163
+ activity_id: start.activity_id,
164
+ activity_type: start.activity_type,
165
+ attempt: start.attempt,
166
+ current_attempt_scheduled_time: Internal::ProtoUtils.timestamp_to_time(
167
+ start.current_attempt_scheduled_time
168
+ ) || raise, # Never nil
169
+ heartbeat_details: ProtoUtils.convert_from_payload_array(
170
+ @worker.options.client.data_converter,
171
+ start.heartbeat_details.to_ary
172
+ ),
173
+ heartbeat_timeout: Internal::ProtoUtils.duration_to_seconds(start.heartbeat_timeout),
174
+ local?: start.is_local,
175
+ schedule_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.schedule_to_close_timeout),
176
+ scheduled_time: Internal::ProtoUtils.timestamp_to_time(start.scheduled_time) || raise, # Never nil
177
+ start_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.start_to_close_timeout),
178
+ started_time: Internal::ProtoUtils.timestamp_to_time(start.started_time) || raise, # Never nil
179
+ task_queue: @worker.options.task_queue,
180
+ task_token:,
181
+ workflow_id: start.workflow_execution.workflow_id,
182
+ workflow_namespace: start.workflow_namespace,
183
+ workflow_run_id: start.workflow_execution.run_id,
184
+ workflow_type: start.workflow_type
185
+ ).freeze
186
+
187
+ # Build input
188
+ input = Temporalio::Worker::Interceptor::Activity::ExecuteInput.new(
189
+ proc: defn.proc,
190
+ # If the activity wants raw_args, we only decode we don't convert
191
+ args: if defn.raw_args
192
+ payloads = start.input.to_ary
193
+ codec = @worker.options.client.data_converter.payload_codec
194
+ payloads = codec.decode(payloads) if codec
195
+ payloads.map { |p| Temporalio::Converters::RawValue.new(p) }
196
+ else
197
+ ProtoUtils.convert_from_payload_array(@worker.options.client.data_converter, start.input.to_ary)
198
+ end,
199
+ headers: ProtoUtils.headers_from_proto_map(start.header_fields, @worker.options.client.data_converter) || {}
200
+ )
201
+
202
+ # Run
203
+ activity = RunningActivity.new(
204
+ info:,
205
+ cancellation: Cancellation.new,
206
+ worker_shutdown_cancellation: @worker._worker_shutdown_cancellation,
207
+ payload_converter: @worker.options.client.data_converter.payload_converter,
208
+ logger: @scoped_logger,
209
+ runtime_metric_meter: @runtime_metric_meter
210
+ )
211
+ Activity::Context._current_executor&.set_activity_context(defn, activity)
212
+ set_running_activity(task_token, activity)
213
+ run_activity(defn, activity, input)
214
+ rescue Exception => e # rubocop:disable Lint/RescueException We are intending to catch everything here
215
+ @scoped_logger.warn("Failed starting or sending completion for activity #{start.activity_type}")
216
+ @scoped_logger.warn(e)
217
+ # This means that the activity couldn't start or send completion (run
218
+ # handles its own errors).
219
+ begin
220
+ @bridge_worker.complete_activity_task(
221
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
222
+ task_token:,
223
+ result: Bridge::Api::ActivityResult::ActivityExecutionResult.new(
224
+ failed: Bridge::Api::ActivityResult::Failure.new(
225
+ failure: @worker.options.client.data_converter.to_failure(e)
226
+ )
227
+ )
228
+ )
229
+ )
230
+ rescue StandardError => e_inner
231
+ @scoped_logger.error("Failed sending failure for activity #{start.activity_type}")
232
+ @scoped_logger.error(e_inner)
233
+ end
234
+ ensure
235
+ Activity::Context._current_executor&.set_activity_context(defn, nil)
236
+ remove_running_activity(task_token)
237
+ end
238
+
239
+ def run_activity(defn, activity, input)
240
+ result = begin
241
+ # Create the instance. We choose to do this before interceptors so that it is available in the interceptor.
242
+ activity.instance = defn.instance.is_a?(Proc) ? defn.instance.call : defn.instance # steep:ignore
243
+
244
+ # Build impl with interceptors
245
+ # @type var impl: Temporalio::Worker::Interceptor::Activity::Inbound
246
+ impl = InboundImplementation.new(self)
247
+ impl = @worker._activity_interceptors.reverse_each.reduce(impl) do |acc, int|
248
+ int.intercept_activity(acc)
249
+ end
250
+ impl.init(OutboundImplementation.new(self))
251
+
252
+ # Execute
253
+ result = impl.execute(input)
254
+
255
+ # Success
256
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
257
+ completed: Bridge::Api::ActivityResult::Success.new(
258
+ result: @worker.options.client.data_converter.to_payload(result)
259
+ )
260
+ )
261
+ rescue Exception => e # rubocop:disable Lint/RescueException We are intending to catch everything here
262
+ if e.is_a?(Activity::CompleteAsyncError)
263
+ # Wanting to complete async
264
+ @scoped_logger.debug('Completing activity asynchronously')
265
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
266
+ will_complete_async: Bridge::Api::ActivityResult::WillCompleteAsync.new
267
+ )
268
+ elsif e.is_a?(Error::CanceledError) && activity._server_requested_cancel
269
+ # Server requested cancel
270
+ @scoped_logger.debug('Completing activity as canceled')
271
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
272
+ cancelled: Bridge::Api::ActivityResult::Cancellation.new(
273
+ failure: @worker.options.client.data_converter.to_failure(e)
274
+ )
275
+ )
276
+ else
277
+ # General failure
278
+ @scoped_logger.warn('Completing activity as failed')
279
+ @scoped_logger.warn(e)
280
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
281
+ failed: Bridge::Api::ActivityResult::Failure.new(
282
+ failure: @worker.options.client.data_converter.to_failure(e)
283
+ )
284
+ )
285
+ end
286
+ end
287
+
288
+ @scoped_logger.debug("Sending activity completion: #{result}") if LOG_TASKS
289
+ @bridge_worker.complete_activity_task(
290
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
291
+ task_token: activity.info.task_token,
292
+ result:
293
+ )
294
+ )
295
+ end
296
+
297
+ class RunningActivity < Activity::Context
298
+ attr_reader :info, :cancellation, :worker_shutdown_cancellation, :payload_converter, :logger
299
+ attr_accessor :instance, :_outbound_impl, :_server_requested_cancel
300
+
301
+ def initialize( # rubocop:disable Lint/MissingSuper
302
+ info:,
303
+ cancellation:,
304
+ worker_shutdown_cancellation:,
305
+ payload_converter:,
306
+ logger:,
307
+ runtime_metric_meter:
308
+ )
309
+ @info = info
310
+ @cancellation = cancellation
311
+ @worker_shutdown_cancellation = worker_shutdown_cancellation
312
+ @payload_converter = payload_converter
313
+ @logger = logger
314
+ @runtime_metric_meter = runtime_metric_meter
315
+ @_outbound_impl = nil
316
+ @_server_requested_cancel = false
317
+ end
318
+
319
+ def heartbeat(*details)
320
+ raise 'Implementation not set yet' if _outbound_impl.nil?
321
+
322
+ # No-op if local
323
+ return if info.local?
324
+
325
+ _outbound_impl.heartbeat(Temporalio::Worker::Interceptor::Activity::HeartbeatInput.new(details:))
326
+ end
327
+
328
+ def metric_meter
329
+ @metric_meter ||= @runtime_metric_meter.with_additional_attributes(
330
+ {
331
+ namespace: info.workflow_namespace,
332
+ task_queue: info.task_queue,
333
+ activity_type: info.activity_type
334
+ }
335
+ )
336
+ end
337
+ end
338
+
339
+ class InboundImplementation < Temporalio::Worker::Interceptor::Activity::Inbound
340
+ def initialize(worker)
341
+ super(nil) # steep:ignore
342
+ @worker = worker
343
+ end
344
+
345
+ def init(outbound)
346
+ context = Activity::Context.current
347
+ raise 'Unexpected context type' unless context.is_a?(RunningActivity)
348
+
349
+ context._outbound_impl = outbound
350
+ end
351
+
352
+ def execute(input)
353
+ input.proc.call(*input.args)
354
+ end
355
+ end
356
+
357
+ class OutboundImplementation < Temporalio::Worker::Interceptor::Activity::Outbound
358
+ def initialize(worker)
359
+ super(nil) # steep:ignore
360
+ @worker = worker
361
+ end
362
+
363
+ def heartbeat(input)
364
+ @worker.bridge_worker.record_activity_heartbeat(
365
+ Bridge::Api::CoreInterface::ActivityHeartbeat.new(
366
+ task_token: Activity::Context.current.info.task_token,
367
+ details: ProtoUtils.convert_to_payload_array(@worker.worker.options.client.data_converter,
368
+ input.details)
369
+ ).to_proto
370
+ )
371
+ end
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end