clara-temporalio 0.4.3-x86_64-linux

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 (190) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +2 -0
  3. data/Gemfile +27 -0
  4. data/Rakefile +101 -0
  5. data/lib/temporalio/activity/cancellation_details.rb +58 -0
  6. data/lib/temporalio/activity/complete_async_error.rb +11 -0
  7. data/lib/temporalio/activity/context.rb +131 -0
  8. data/lib/temporalio/activity/definition.rb +197 -0
  9. data/lib/temporalio/activity/info.rb +70 -0
  10. data/lib/temporalio/activity.rb +14 -0
  11. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  12. data/lib/temporalio/api/batch/v1/message.rb +38 -0
  13. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  14. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +135 -0
  15. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
  16. data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
  17. data/lib/temporalio/api/cloud/identity/v1/message.rb +46 -0
  18. data/lib/temporalio/api/cloud/namespace/v1/message.rb +46 -0
  19. data/lib/temporalio/api/cloud/nexus/v1/message.rb +32 -0
  20. data/lib/temporalio/api/cloud/operation/v1/message.rb +28 -0
  21. data/lib/temporalio/api/cloud/region/v1/message.rb +24 -0
  22. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  23. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  24. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  25. data/lib/temporalio/api/command/v1/message.rb +46 -0
  26. data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
  27. data/lib/temporalio/api/common/v1/message.rb +49 -0
  28. data/lib/temporalio/api/deployment/v1/message.rb +39 -0
  29. data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
  30. data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
  31. data/lib/temporalio/api/enums/v1/common.rb +28 -0
  32. data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
  33. data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
  34. data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
  35. data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
  36. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  37. data/lib/temporalio/api/enums/v1/query.rb +22 -0
  38. data/lib/temporalio/api/enums/v1/reset.rb +23 -0
  39. data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
  40. data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
  41. data/lib/temporalio/api/enums/v1/update.rb +22 -0
  42. data/lib/temporalio/api/enums/v1/workflow.rb +31 -0
  43. data/lib/temporalio/api/errordetails/v1/message.rb +44 -0
  44. data/lib/temporalio/api/export/v1/message.rb +24 -0
  45. data/lib/temporalio/api/failure/v1/message.rb +38 -0
  46. data/lib/temporalio/api/filter/v1/message.rb +27 -0
  47. data/lib/temporalio/api/history/v1/message.rb +94 -0
  48. data/lib/temporalio/api/namespace/v1/message.rb +31 -0
  49. data/lib/temporalio/api/nexus/v1/message.rb +41 -0
  50. data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
  51. data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
  52. data/lib/temporalio/api/operatorservice.rb +3 -0
  53. data/lib/temporalio/api/payload_visitor.rb +1668 -0
  54. data/lib/temporalio/api/protocol/v1/message.rb +23 -0
  55. data/lib/temporalio/api/query/v1/message.rb +28 -0
  56. data/lib/temporalio/api/replication/v1/message.rb +26 -0
  57. data/lib/temporalio/api/rules/v1/message.rb +27 -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 +48 -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 +63 -0
  69. data/lib/temporalio/api/workflowservice/v1/request_response.rb +244 -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 +15 -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 +786 -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 +1326 -0
  81. data/lib/temporalio/client/connection.rb +316 -0
  82. data/lib/temporalio/client/interceptor.rb +457 -0
  83. data/lib/temporalio/client/schedule.rb +991 -0
  84. data/lib/temporalio/client/schedule_handle.rb +126 -0
  85. data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
  86. data/lib/temporalio/client/workflow_execution.rb +119 -0
  87. data/lib/temporalio/client/workflow_execution_count.rb +36 -0
  88. data/lib/temporalio/client/workflow_execution_status.rb +18 -0
  89. data/lib/temporalio/client/workflow_handle.rb +389 -0
  90. data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
  91. data/lib/temporalio/client/workflow_update_handle.rb +65 -0
  92. data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
  93. data/lib/temporalio/client.rb +625 -0
  94. data/lib/temporalio/common_enums.rb +55 -0
  95. data/lib/temporalio/contrib/open_telemetry.rb +469 -0
  96. data/lib/temporalio/converters/data_converter.rb +99 -0
  97. data/lib/temporalio/converters/failure_converter.rb +205 -0
  98. data/lib/temporalio/converters/payload_codec.rb +26 -0
  99. data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
  100. data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
  101. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
  102. data/lib/temporalio/converters/payload_converter/composite.rb +66 -0
  103. data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
  104. data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
  105. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
  106. data/lib/temporalio/converters/payload_converter.rb +71 -0
  107. data/lib/temporalio/converters/raw_value.rb +20 -0
  108. data/lib/temporalio/converters.rb +9 -0
  109. data/lib/temporalio/error/failure.rb +237 -0
  110. data/lib/temporalio/error.rb +156 -0
  111. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  112. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  113. data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +0 -0
  114. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
  115. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +32 -0
  116. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
  117. data/lib/temporalio/internal/bridge/api/common/common.rb +27 -0
  118. data/lib/temporalio/internal/bridge/api/core_interface.rb +40 -0
  119. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
  120. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +34 -0
  121. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +56 -0
  122. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +58 -0
  123. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +31 -0
  124. data/lib/temporalio/internal/bridge/api.rb +3 -0
  125. data/lib/temporalio/internal/bridge/client.rb +95 -0
  126. data/lib/temporalio/internal/bridge/runtime.rb +56 -0
  127. data/lib/temporalio/internal/bridge/testing.rb +69 -0
  128. data/lib/temporalio/internal/bridge/worker.rb +109 -0
  129. data/lib/temporalio/internal/bridge.rb +36 -0
  130. data/lib/temporalio/internal/client/implementation.rb +926 -0
  131. data/lib/temporalio/internal/metric.rb +122 -0
  132. data/lib/temporalio/internal/proto_utils.rb +165 -0
  133. data/lib/temporalio/internal/worker/activity_worker.rb +448 -0
  134. data/lib/temporalio/internal/worker/multi_runner.rb +213 -0
  135. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  136. data/lib/temporalio/internal/worker/workflow_instance/context.rb +391 -0
  137. data/lib/temporalio/internal/worker/workflow_instance/details.rb +49 -0
  138. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  139. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  140. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  141. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  142. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  143. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  144. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +404 -0
  145. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  146. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  147. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +183 -0
  148. data/lib/temporalio/internal/worker/workflow_instance.rb +800 -0
  149. data/lib/temporalio/internal/worker/workflow_worker.rb +249 -0
  150. data/lib/temporalio/internal.rb +7 -0
  151. data/lib/temporalio/metric.rb +109 -0
  152. data/lib/temporalio/priority.rb +59 -0
  153. data/lib/temporalio/retry_policy.rb +74 -0
  154. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  155. data/lib/temporalio/runtime.rb +352 -0
  156. data/lib/temporalio/scoped_logger.rb +96 -0
  157. data/lib/temporalio/search_attributes.rb +356 -0
  158. data/lib/temporalio/testing/activity_environment.rb +175 -0
  159. data/lib/temporalio/testing/workflow_environment.rb +406 -0
  160. data/lib/temporalio/testing.rb +10 -0
  161. data/lib/temporalio/version.rb +5 -0
  162. data/lib/temporalio/versioning_override.rb +55 -0
  163. data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
  164. data/lib/temporalio/worker/activity_executor/thread_pool.rb +46 -0
  165. data/lib/temporalio/worker/activity_executor.rb +55 -0
  166. data/lib/temporalio/worker/deployment_options.rb +45 -0
  167. data/lib/temporalio/worker/interceptor.rb +367 -0
  168. data/lib/temporalio/worker/poller_behavior.rb +61 -0
  169. data/lib/temporalio/worker/thread_pool.rb +237 -0
  170. data/lib/temporalio/worker/tuner.rb +189 -0
  171. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +236 -0
  172. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  173. data/lib/temporalio/worker/workflow_replayer.rb +349 -0
  174. data/lib/temporalio/worker.rb +633 -0
  175. data/lib/temporalio/worker_deployment_version.rb +67 -0
  176. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  177. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  178. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  179. data/lib/temporalio/workflow/definition.rb +680 -0
  180. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  181. data/lib/temporalio/workflow/future.rb +151 -0
  182. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  183. data/lib/temporalio/workflow/info.rb +107 -0
  184. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  185. data/lib/temporalio/workflow/update_info.rb +20 -0
  186. data/lib/temporalio/workflow.rb +594 -0
  187. data/lib/temporalio/workflow_history.rb +47 -0
  188. data/lib/temporalio.rb +12 -0
  189. data/temporalio.gemspec +31 -0
  190. metadata +267 -0
@@ -0,0 +1,448 @@
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 not found and not reserved name
97
+ defn = @activities[start.activity_type]
98
+ defn = @activities[nil] if !defn && !Internal::ProtoUtils.reserved_name?(start.activity_type)
99
+
100
+ if defn.nil?
101
+ raise Error::ApplicationError.new(
102
+ "Activity #{start.activity_type} for workflow #{start.workflow_execution.workflow_id} " \
103
+ "is not registered on this worker, available activities: #{@activities.keys.sort.join(', ')}",
104
+ type: 'NotFoundError'
105
+ )
106
+ end
107
+
108
+ # Run everything else in the excecutor
109
+ executor = @worker.options.activity_executors[defn.executor]
110
+ executor.execute_activity(defn) do
111
+ # Set current executor
112
+ Activity::Context._current_executor = executor
113
+ # Execute with error handling
114
+ execute_activity(task_token, defn, start)
115
+ ensure
116
+ # Unset at the end
117
+ Activity::Context._current_executor = nil
118
+ end
119
+ rescue Exception => e # rubocop:disable Lint/RescueException -- We are intending to catch everything here
120
+ remove_running_activity(task_token)
121
+ @scoped_logger.warn("Failed starting activity #{start.activity_type}")
122
+ @scoped_logger.warn(e)
123
+
124
+ # We need to complete the activity task as failed, but this is on the
125
+ # hot path for polling, so we want to complete it in the background
126
+ begin
127
+ @bridge_worker.complete_activity_task_in_background(
128
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
129
+ task_token:,
130
+ result: Bridge::Api::ActivityResult::ActivityExecutionResult.new(
131
+ failed: Bridge::Api::ActivityResult::Failure.new(
132
+ # TODO(cretz): If failure conversion does slow failure
133
+ # encoding, it can gum up the system
134
+ failure: @worker.options.client.data_converter.to_failure(e)
135
+ )
136
+ )
137
+ )
138
+ )
139
+ rescue StandardError => e_inner
140
+ @scoped_logger.error("Failed building start failure to return for #{start.activity_type}")
141
+ @scoped_logger.error(e_inner)
142
+ end
143
+ end
144
+
145
+ def handle_cancel_task(task_token, cancel)
146
+ activity = get_running_activity(task_token)
147
+ if activity.nil?
148
+ @scoped_logger.warn("Cannot find activity to cancel for token #{task_token}")
149
+ return
150
+ end
151
+ begin
152
+ activity._cancel(
153
+ reason: cancel.reason.to_s,
154
+ details: Activity::CancellationDetails.new(
155
+ gone_from_server: cancel.details.is_not_found,
156
+ cancel_requested: cancel.details.is_cancelled,
157
+ timed_out: cancel.details.is_timed_out,
158
+ worker_shutdown: cancel.details.is_worker_shutdown,
159
+ paused: cancel.details.is_paused,
160
+ reset: cancel.details.is_reset
161
+ )
162
+ )
163
+ rescue StandardError => e
164
+ @scoped_logger.warn("Failed cancelling activity #{activity.info.activity_type} \
165
+ with ID #{activity.info.activity_id}")
166
+ @scoped_logger.warn(e)
167
+ end
168
+ end
169
+
170
+ def execute_activity(task_token, defn, start)
171
+ # Build info
172
+ info = Activity::Info.new(
173
+ activity_id: start.activity_id,
174
+ activity_type: start.activity_type,
175
+ attempt: start.attempt,
176
+ current_attempt_scheduled_time: Internal::ProtoUtils.timestamp_to_time(
177
+ start.current_attempt_scheduled_time
178
+ ) || raise, # Never nil
179
+ heartbeat_details: ProtoUtils.convert_from_payload_array(
180
+ @worker.options.client.data_converter,
181
+ start.heartbeat_details.to_ary
182
+ ),
183
+ heartbeat_timeout: Internal::ProtoUtils.duration_to_seconds(start.heartbeat_timeout),
184
+ local?: start.is_local,
185
+ priority: Priority._from_proto(start.priority),
186
+ schedule_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.schedule_to_close_timeout),
187
+ scheduled_time: Internal::ProtoUtils.timestamp_to_time(start.scheduled_time) || raise, # Never nil
188
+ start_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.start_to_close_timeout),
189
+ started_time: Internal::ProtoUtils.timestamp_to_time(start.started_time) || raise, # Never nil
190
+ task_queue: @worker.options.task_queue,
191
+ task_token:,
192
+ workflow_id: start.workflow_execution.workflow_id,
193
+ workflow_namespace: start.workflow_namespace,
194
+ workflow_run_id: start.workflow_execution.run_id,
195
+ workflow_type: start.workflow_type
196
+ ).freeze
197
+
198
+ # Build input
199
+ input = Temporalio::Worker::Interceptor::Activity::ExecuteInput.new(
200
+ proc: defn.proc,
201
+ # If the activity wants raw_args, we only decode we don't convert
202
+ args: if defn.raw_args
203
+ payloads = start.input.to_ary
204
+ codec = @worker.options.client.data_converter.payload_codec
205
+ payloads = codec.decode(payloads) if codec
206
+ payloads.map { |p| Temporalio::Converters::RawValue.new(p) }
207
+ else
208
+ ProtoUtils.convert_from_payload_array(@worker.options.client.data_converter, start.input.to_ary)
209
+ end,
210
+ headers: ProtoUtils.headers_from_proto_map(start.header_fields, @worker.options.client.data_converter) || {}
211
+ )
212
+
213
+ # Run
214
+ activity = RunningActivity.new(
215
+ worker: @worker,
216
+ info:,
217
+ cancellation: Cancellation.new,
218
+ worker_shutdown_cancellation: @worker._worker_shutdown_cancellation,
219
+ payload_converter: @worker.options.client.data_converter.payload_converter,
220
+ logger: @scoped_logger,
221
+ runtime_metric_meter: @runtime_metric_meter
222
+ )
223
+ Activity::Context._current_executor&.set_activity_context(defn, activity)
224
+ set_running_activity(task_token, activity)
225
+ run_activity(defn, activity, input)
226
+ rescue Exception => e # rubocop:disable Lint/RescueException -- We are intending to catch everything here
227
+ @scoped_logger.warn("Failed starting or sending completion for activity #{start.activity_type}")
228
+ @scoped_logger.warn(e)
229
+ # This means that the activity couldn't start or send completion (run
230
+ # handles its own errors).
231
+ begin
232
+ @bridge_worker.complete_activity_task(
233
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
234
+ task_token:,
235
+ result: Bridge::Api::ActivityResult::ActivityExecutionResult.new(
236
+ failed: Bridge::Api::ActivityResult::Failure.new(
237
+ failure: @worker.options.client.data_converter.to_failure(e)
238
+ )
239
+ )
240
+ )
241
+ )
242
+ rescue StandardError => e_inner
243
+ @scoped_logger.error("Failed sending failure for activity #{start.activity_type}")
244
+ @scoped_logger.error(e_inner)
245
+ end
246
+ ensure
247
+ Activity::Context._current_executor&.set_activity_context(defn, nil)
248
+ remove_running_activity(task_token)
249
+ end
250
+
251
+ def run_activity(defn, activity, input)
252
+ result = begin
253
+ # Create the instance. We choose to do this before interceptors so that it is available in the interceptor.
254
+ activity.instance = defn.instance.is_a?(Proc) ? defn.instance.call : defn.instance # steep:ignore
255
+
256
+ # Build impl with interceptors
257
+ # @type var impl: Temporalio::Worker::Interceptor::Activity::Inbound
258
+ impl = InboundImplementation.new(self)
259
+ impl = @worker._activity_interceptors.reverse_each.reduce(impl) do |acc, int|
260
+ int.intercept_activity(acc)
261
+ end
262
+ impl.init(OutboundImplementation.new(self, activity.info.task_token))
263
+
264
+ # Execute
265
+ result = impl.execute(input)
266
+
267
+ # Success
268
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
269
+ completed: Bridge::Api::ActivityResult::Success.new(
270
+ result: @worker.options.client.data_converter.to_payload(result)
271
+ )
272
+ )
273
+ rescue Exception => e # rubocop:disable Lint/RescueException -- We are intending to catch everything here
274
+ if e.is_a?(Activity::CompleteAsyncError)
275
+ # Wanting to complete async
276
+ @scoped_logger.debug('Completing activity asynchronously')
277
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
278
+ will_complete_async: Bridge::Api::ActivityResult::WillCompleteAsync.new
279
+ )
280
+ elsif e.is_a?(Error::CanceledError) && activity.cancellation_details&.paused?
281
+ # Server requested pause
282
+ @scoped_logger.debug('Completing activity as failed due to exception caused by pause')
283
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
284
+ failed: Bridge::Api::ActivityResult::Failure.new(
285
+ failure: @worker.options.client.data_converter.to_failure(
286
+ Error._with_backtrace_and_cause(
287
+ Error::ApplicationError.new('Activity paused', type: 'ActivityPause'), backtrace: nil, cause: e
288
+ )
289
+ )
290
+ )
291
+ )
292
+ elsif e.is_a?(Error::CanceledError) && activity.cancellation_details&.reset?
293
+ # Server requested reset
294
+ @scoped_logger.debug('Completing activity as failed due to exception caused by reset')
295
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
296
+ failed: Bridge::Api::ActivityResult::Failure.new(
297
+ failure: @worker.options.client.data_converter.to_failure(
298
+ Error._with_backtrace_and_cause(
299
+ Error::ApplicationError.new('Activity reset', type: 'ActivityReset'), backtrace: nil, cause: e
300
+ )
301
+ )
302
+ )
303
+ )
304
+ elsif e.is_a?(Error::CanceledError) && activity._server_requested_cancel
305
+ # Server requested cancel
306
+ @scoped_logger.debug('Completing activity as canceled')
307
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
308
+ cancelled: Bridge::Api::ActivityResult::Cancellation.new(
309
+ failure: @worker.options.client.data_converter.to_failure(e)
310
+ )
311
+ )
312
+ else
313
+ # General failure
314
+ log_level = if e.is_a?(Error::ApplicationError) && e.category == Error::ApplicationError::Category::BENIGN
315
+ Logger::DEBUG
316
+ else
317
+ Logger::WARN
318
+ end
319
+ @scoped_logger.add(log_level, 'Completing activity as failed')
320
+ @scoped_logger.add(log_level, e)
321
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
322
+ failed: Bridge::Api::ActivityResult::Failure.new(
323
+ failure: @worker.options.client.data_converter.to_failure(e)
324
+ )
325
+ )
326
+ end
327
+ end
328
+
329
+ @scoped_logger.debug("Sending activity completion: #{result}") if LOG_TASKS
330
+ @bridge_worker.complete_activity_task(
331
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
332
+ task_token: activity.info.task_token,
333
+ result:
334
+ )
335
+ )
336
+ end
337
+
338
+ def assert_valid_activity(activity)
339
+ defn = @activities[activity]
340
+ defn = @activities[nil] if !defn && !Internal::ProtoUtils.reserved_name?(activity)
341
+
342
+ return unless defn.nil?
343
+
344
+ raise ArgumentError,
345
+ "Activity #{activity} " \
346
+ "is not registered on this worker, available activities: #{@activities.keys.sort.join(', ')}"
347
+ end
348
+
349
+ class RunningActivity < Activity::Context
350
+ attr_reader :info, :cancellation, :cancellation_details, :worker_shutdown_cancellation,
351
+ :payload_converter, :logger, :_server_requested_cancel
352
+ attr_accessor :instance, :_outbound_impl
353
+
354
+ def initialize( # rubocop:disable Lint/MissingSuper
355
+ worker:,
356
+ info:,
357
+ cancellation:,
358
+ worker_shutdown_cancellation:,
359
+ payload_converter:,
360
+ logger:,
361
+ runtime_metric_meter:
362
+ )
363
+ @worker = worker
364
+ @info = info
365
+ @cancellation = cancellation
366
+ @cancellation_details = nil
367
+ @worker_shutdown_cancellation = worker_shutdown_cancellation
368
+ @payload_converter = payload_converter
369
+ @logger = logger
370
+ @runtime_metric_meter = runtime_metric_meter
371
+ @_outbound_impl = nil
372
+ @_server_requested_cancel = false
373
+ end
374
+
375
+ def heartbeat(*details)
376
+ raise 'Implementation not set yet' if _outbound_impl.nil?
377
+
378
+ # No-op if local
379
+ return if info.local?
380
+
381
+ _outbound_impl.heartbeat(Temporalio::Worker::Interceptor::Activity::HeartbeatInput.new(details:))
382
+ end
383
+
384
+ def metric_meter
385
+ @metric_meter ||= @runtime_metric_meter.with_additional_attributes(
386
+ {
387
+ namespace: info.workflow_namespace,
388
+ task_queue: info.task_queue,
389
+ activity_type: info.activity_type
390
+ }
391
+ )
392
+ end
393
+
394
+ def client
395
+ @worker.client
396
+ end
397
+
398
+ def _cancel(reason:, details:)
399
+ # Do not issue cancel if already canceled
400
+ return if @cancellation_details
401
+
402
+ @_server_requested_cancel = true
403
+ # Set the cancellation details _before_ issuing the cancel itself
404
+ @cancellation_details = details
405
+ _, cancel_proc = cancellation
406
+ cancel_proc.call(reason:)
407
+ end
408
+ end
409
+
410
+ class InboundImplementation < Temporalio::Worker::Interceptor::Activity::Inbound
411
+ def initialize(worker)
412
+ super(nil) # steep:ignore
413
+ @worker = worker
414
+ end
415
+
416
+ def init(outbound)
417
+ context = Activity::Context.current
418
+ raise 'Unexpected context type' unless context.is_a?(RunningActivity)
419
+
420
+ context._outbound_impl = outbound
421
+ end
422
+
423
+ def execute(input)
424
+ input.proc.call(*input.args)
425
+ end
426
+ end
427
+
428
+ class OutboundImplementation < Temporalio::Worker::Interceptor::Activity::Outbound
429
+ def initialize(worker, task_token)
430
+ super(nil) # steep:ignore
431
+ @worker = worker
432
+ @task_token = task_token
433
+ end
434
+
435
+ def heartbeat(input)
436
+ @worker.bridge_worker.record_activity_heartbeat(
437
+ Bridge::Api::CoreInterface::ActivityHeartbeat.new(
438
+ task_token: @task_token,
439
+ details: ProtoUtils.convert_to_payload_array(@worker.worker.options.client.data_converter,
440
+ input.details)
441
+ ).to_proto
442
+ )
443
+ end
444
+ end
445
+ end
446
+ end
447
+ end
448
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'temporalio/internal/bridge/worker'
5
+
6
+ module Temporalio
7
+ module Internal
8
+ module Worker
9
+ # Primary worker (re)actor-style event handler. This handles multiple workers, receiving events from the bridge,
10
+ # and handling a user block.
11
+ class MultiRunner
12
+ def initialize(workers:, shutdown_signals:)
13
+ @workers = workers
14
+ @queue = Queue.new
15
+
16
+ @shutdown_initiated_mutex = Mutex.new
17
+ @shutdown_initiated = false
18
+
19
+ # Trap signals to push to queue
20
+ shutdown_signals.each do |signal|
21
+ Signal.trap(signal) { @queue.push(Event::ShutdownSignalReceived.new) }
22
+ end
23
+
24
+ # Start pollers
25
+ Bridge::Worker.async_poll_all(workers.map(&:_bridge_worker), @queue)
26
+ end
27
+
28
+ def apply_thread_or_fiber_block(&)
29
+ return unless block_given?
30
+
31
+ @thread_or_fiber = if Fiber.current_scheduler
32
+ Fiber.schedule do
33
+ @queue.push(Event::BlockSuccess.new(result: yield))
34
+ rescue InjectEventForTesting => e
35
+ @queue.push(e.event)
36
+ @queue.push(Event::BlockSuccess.new(result: e))
37
+ rescue Exception => e # rubocop:disable Lint/RescueException -- Intentionally catch all
38
+ @queue.push(Event::BlockFailure.new(error: e))
39
+ end
40
+ else
41
+ Thread.new do
42
+ @queue.push(Event::BlockSuccess.new(result: yield))
43
+ rescue InjectEventForTesting => e
44
+ @queue.push(e.event)
45
+ @queue.push(Event::BlockSuccess.new(result: e))
46
+ rescue Exception => e # rubocop:disable Lint/RescueException -- Intentionally catch all
47
+ @queue.push(Event::BlockFailure.new(error: e))
48
+ end
49
+ end
50
+ end
51
+
52
+ def apply_workflow_activation_decoded(workflow_worker:, activation:)
53
+ @queue.push(Event::WorkflowActivationDecoded.new(workflow_worker:, activation:))
54
+ end
55
+
56
+ def apply_workflow_activation_complete(workflow_worker:, activation_completion:, encoded:)
57
+ @queue.push(Event::WorkflowActivationComplete.new(
58
+ workflow_worker:, activation_completion:, encoded:, completion_complete_queue: @queue
59
+ ))
60
+ end
61
+
62
+ def raise_in_thread_or_fiber_block(error)
63
+ @thread_or_fiber&.raise(error)
64
+ end
65
+
66
+ # Clarify this is the only thread-safe function here
67
+ def initiate_shutdown
68
+ should_call = @shutdown_initiated_mutex.synchronize do
69
+ break false if @shutdown_initiated
70
+
71
+ @shutdown_initiated = true
72
+ end
73
+ return unless should_call
74
+
75
+ @workers.each(&:_initiate_shutdown)
76
+ end
77
+
78
+ def wait_complete_and_finalize_shutdown
79
+ # Wait for them all to complete
80
+ @workers.each(&:_wait_all_complete)
81
+
82
+ # Finalize them all
83
+ Bridge::Worker.finalize_shutdown_all(@workers.map(&:_bridge_worker))
84
+ end
85
+
86
+ # Intentionally not an enumerable/enumerator since stop semantics are
87
+ # caller determined
88
+ def next_event
89
+ # Queue value is one of the following:
90
+ # * Event - non-poller event
91
+ # * [worker index, :activity/:workflow, bytes] - poll success
92
+ # * [worker index, :activity/:workflow, error] - poll fail
93
+ # * [worker index, :activity/:workflow, nil] - worker shutdown
94
+ # * [nil, nil, nil] - all pollers done
95
+ # * [-1, run_id_string, error_or_nil] - workflow activation completion complete
96
+ result = @queue.pop
97
+ if result.is_a?(Event)
98
+ result
99
+ else
100
+ first, second, third = result
101
+ if first.nil? || second.nil?
102
+ Event::AllPollersShutDown.instance
103
+ elsif first == -1
104
+ Event::WorkflowActivationCompletionComplete.new(run_id: second, error: third)
105
+ else
106
+ worker = @workers[first]
107
+ case third
108
+ when nil
109
+ Event::PollerShutDown.new(worker:, worker_type: second)
110
+ when Exception
111
+ Event::PollFailure.new(worker:, worker_type: second, error: third)
112
+ else
113
+ Event::PollSuccess.new(worker:, worker_type: second, bytes: third)
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ class Event
120
+ class PollSuccess < Event
121
+ attr_reader :worker, :worker_type, :bytes
122
+
123
+ def initialize(worker:, worker_type:, bytes:) # rubocop:disable Lint/MissingSuper
124
+ @worker = worker
125
+ @worker_type = worker_type
126
+ @bytes = bytes
127
+ end
128
+ end
129
+
130
+ class PollFailure < Event
131
+ attr_reader :worker, :worker_type, :error
132
+
133
+ def initialize(worker:, worker_type:, error:) # rubocop:disable Lint/MissingSuper
134
+ @worker = worker
135
+ @worker_type = worker_type
136
+ @error = error
137
+ end
138
+ end
139
+
140
+ class WorkflowActivationDecoded < Event
141
+ attr_reader :workflow_worker, :activation
142
+
143
+ def initialize(workflow_worker:, activation:) # rubocop:disable Lint/MissingSuper
144
+ @workflow_worker = workflow_worker
145
+ @activation = activation
146
+ end
147
+ end
148
+
149
+ class WorkflowActivationComplete < Event
150
+ attr_reader :workflow_worker, :activation_completion, :encoded, :completion_complete_queue
151
+
152
+ def initialize(workflow_worker:, activation_completion:, encoded:, completion_complete_queue:) # rubocop:disable Lint/MissingSuper
153
+ @workflow_worker = workflow_worker
154
+ @activation_completion = activation_completion
155
+ @encoded = encoded
156
+ @completion_complete_queue = completion_complete_queue
157
+ end
158
+ end
159
+
160
+ class WorkflowActivationCompletionComplete < Event
161
+ attr_reader :run_id, :error
162
+
163
+ def initialize(run_id:, error:) # rubocop:disable Lint/MissingSuper
164
+ @run_id = run_id
165
+ @error = error
166
+ end
167
+ end
168
+
169
+ class PollerShutDown < Event
170
+ attr_reader :worker, :worker_type
171
+
172
+ def initialize(worker:, worker_type:) # rubocop:disable Lint/MissingSuper
173
+ @worker = worker
174
+ @worker_type = worker_type
175
+ end
176
+ end
177
+
178
+ class AllPollersShutDown < Event
179
+ include Singleton
180
+ end
181
+
182
+ class BlockSuccess < Event
183
+ attr_reader :result
184
+
185
+ def initialize(result:) # rubocop:disable Lint/MissingSuper
186
+ @result = result
187
+ end
188
+ end
189
+
190
+ class BlockFailure < Event
191
+ attr_reader :error
192
+
193
+ def initialize(error:) # rubocop:disable Lint/MissingSuper
194
+ @error = error
195
+ end
196
+ end
197
+
198
+ class ShutdownSignalReceived < Event
199
+ end
200
+ end
201
+
202
+ class InjectEventForTesting < Temporalio::Error
203
+ attr_reader :event
204
+
205
+ def initialize(event)
206
+ super('Injecting event for testing')
207
+ @event = event
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end