temporalio 0.2.0-aarch64-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 (130) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +23 -0
  3. data/Rakefile +387 -0
  4. data/lib/temporalio/activity/complete_async_error.rb +11 -0
  5. data/lib/temporalio/activity/context.rb +107 -0
  6. data/lib/temporalio/activity/definition.rb +77 -0
  7. data/lib/temporalio/activity/info.rb +63 -0
  8. data/lib/temporalio/activity.rb +69 -0
  9. data/lib/temporalio/api/batch/v1/message.rb +31 -0
  10. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +93 -0
  11. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
  12. data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
  13. data/lib/temporalio/api/cloud/identity/v1/message.rb +36 -0
  14. data/lib/temporalio/api/cloud/namespace/v1/message.rb +35 -0
  15. data/lib/temporalio/api/cloud/operation/v1/message.rb +27 -0
  16. data/lib/temporalio/api/cloud/region/v1/message.rb +23 -0
  17. data/lib/temporalio/api/command/v1/message.rb +46 -0
  18. data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
  19. data/lib/temporalio/api/common/v1/message.rb +41 -0
  20. data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
  21. data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
  22. data/lib/temporalio/api/enums/v1/common.rb +26 -0
  23. data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
  24. data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
  25. data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
  26. data/lib/temporalio/api/enums/v1/query.rb +22 -0
  27. data/lib/temporalio/api/enums/v1/reset.rb +23 -0
  28. data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
  29. data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
  30. data/lib/temporalio/api/enums/v1/update.rb +22 -0
  31. data/lib/temporalio/api/enums/v1/workflow.rb +30 -0
  32. data/lib/temporalio/api/errordetails/v1/message.rb +42 -0
  33. data/lib/temporalio/api/export/v1/message.rb +24 -0
  34. data/lib/temporalio/api/failure/v1/message.rb +35 -0
  35. data/lib/temporalio/api/filter/v1/message.rb +27 -0
  36. data/lib/temporalio/api/history/v1/message.rb +90 -0
  37. data/lib/temporalio/api/namespace/v1/message.rb +31 -0
  38. data/lib/temporalio/api/nexus/v1/message.rb +40 -0
  39. data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
  40. data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
  41. data/lib/temporalio/api/operatorservice.rb +3 -0
  42. data/lib/temporalio/api/protocol/v1/message.rb +23 -0
  43. data/lib/temporalio/api/query/v1/message.rb +27 -0
  44. data/lib/temporalio/api/replication/v1/message.rb +26 -0
  45. data/lib/temporalio/api/schedule/v1/message.rb +42 -0
  46. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +25 -0
  47. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +21 -0
  48. data/lib/temporalio/api/sdk/v1/user_metadata.rb +23 -0
  49. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +23 -0
  50. data/lib/temporalio/api/taskqueue/v1/message.rb +45 -0
  51. data/lib/temporalio/api/update/v1/message.rb +33 -0
  52. data/lib/temporalio/api/version/v1/message.rb +26 -0
  53. data/lib/temporalio/api/workflow/v1/message.rb +43 -0
  54. data/lib/temporalio/api/workflowservice/v1/request_response.rb +189 -0
  55. data/lib/temporalio/api/workflowservice/v1/service.rb +23 -0
  56. data/lib/temporalio/api/workflowservice.rb +3 -0
  57. data/lib/temporalio/api.rb +13 -0
  58. data/lib/temporalio/cancellation.rb +150 -0
  59. data/lib/temporalio/client/activity_id_reference.rb +32 -0
  60. data/lib/temporalio/client/async_activity_handle.rb +110 -0
  61. data/lib/temporalio/client/connection/cloud_service.rb +648 -0
  62. data/lib/temporalio/client/connection/operator_service.rb +249 -0
  63. data/lib/temporalio/client/connection/service.rb +41 -0
  64. data/lib/temporalio/client/connection/workflow_service.rb +1218 -0
  65. data/lib/temporalio/client/connection.rb +270 -0
  66. data/lib/temporalio/client/interceptor.rb +316 -0
  67. data/lib/temporalio/client/workflow_execution.rb +103 -0
  68. data/lib/temporalio/client/workflow_execution_count.rb +36 -0
  69. data/lib/temporalio/client/workflow_execution_status.rb +18 -0
  70. data/lib/temporalio/client/workflow_handle.rb +446 -0
  71. data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
  72. data/lib/temporalio/client/workflow_update_handle.rb +67 -0
  73. data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
  74. data/lib/temporalio/client.rb +404 -0
  75. data/lib/temporalio/common_enums.rb +24 -0
  76. data/lib/temporalio/converters/data_converter.rb +102 -0
  77. data/lib/temporalio/converters/failure_converter.rb +200 -0
  78. data/lib/temporalio/converters/payload_codec.rb +26 -0
  79. data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
  80. data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
  81. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
  82. data/lib/temporalio/converters/payload_converter/composite.rb +62 -0
  83. data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
  84. data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
  85. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
  86. data/lib/temporalio/converters/payload_converter.rb +73 -0
  87. data/lib/temporalio/converters.rb +9 -0
  88. data/lib/temporalio/error/failure.rb +219 -0
  89. data/lib/temporalio/error.rb +147 -0
  90. data/lib/temporalio/internal/bridge/3.1/temporalio_bridge.so +0 -0
  91. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  92. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  93. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
  94. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +31 -0
  95. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
  96. data/lib/temporalio/internal/bridge/api/common/common.rb +26 -0
  97. data/lib/temporalio/internal/bridge/api/core_interface.rb +36 -0
  98. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
  99. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +52 -0
  100. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +54 -0
  101. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +30 -0
  102. data/lib/temporalio/internal/bridge/api.rb +3 -0
  103. data/lib/temporalio/internal/bridge/client.rb +90 -0
  104. data/lib/temporalio/internal/bridge/runtime.rb +53 -0
  105. data/lib/temporalio/internal/bridge/testing.rb +46 -0
  106. data/lib/temporalio/internal/bridge/worker.rb +83 -0
  107. data/lib/temporalio/internal/bridge.rb +36 -0
  108. data/lib/temporalio/internal/client/implementation.rb +525 -0
  109. data/lib/temporalio/internal/proto_utils.rb +54 -0
  110. data/lib/temporalio/internal/worker/activity_worker.rb +345 -0
  111. data/lib/temporalio/internal/worker/multi_runner.rb +169 -0
  112. data/lib/temporalio/internal.rb +7 -0
  113. data/lib/temporalio/retry_policy.rb +51 -0
  114. data/lib/temporalio/runtime.rb +271 -0
  115. data/lib/temporalio/scoped_logger.rb +96 -0
  116. data/lib/temporalio/search_attributes.rb +300 -0
  117. data/lib/temporalio/testing/activity_environment.rb +132 -0
  118. data/lib/temporalio/testing/workflow_environment.rb +137 -0
  119. data/lib/temporalio/testing.rb +10 -0
  120. data/lib/temporalio/version.rb +5 -0
  121. data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
  122. data/lib/temporalio/worker/activity_executor/thread_pool.rb +254 -0
  123. data/lib/temporalio/worker/activity_executor.rb +55 -0
  124. data/lib/temporalio/worker/interceptor.rb +88 -0
  125. data/lib/temporalio/worker/tuner.rb +151 -0
  126. data/lib/temporalio/worker.rb +426 -0
  127. data/lib/temporalio/workflow_history.rb +22 -0
  128. data/lib/temporalio.rb +7 -0
  129. data/temporalio.gemspec +28 -0
  130. metadata +191 -0
@@ -0,0 +1,345 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/activity'
4
+ require 'temporalio/activity/definition'
5
+ require 'temporalio/cancellation'
6
+ require 'temporalio/internal/bridge/api'
7
+ require 'temporalio/internal/proto_utils'
8
+ require 'temporalio/scoped_logger'
9
+ require 'temporalio/worker/interceptor'
10
+
11
+ module Temporalio
12
+ module Internal
13
+ module Worker
14
+ class ActivityWorker
15
+ LOG_TASKS = false
16
+
17
+ attr_reader :worker, :bridge_worker
18
+
19
+ def initialize(worker, bridge_worker)
20
+ @worker = worker
21
+ @bridge_worker = bridge_worker
22
+
23
+ # Create shared logger that gives scoped activity details
24
+ @scoped_logger = ScopedLogger.new(@worker.options.logger)
25
+ @scoped_logger.scoped_values_getter = proc {
26
+ Activity::Context.current_or_nil&._scoped_logger_info
27
+ }
28
+
29
+ # Build up activity hash by name, failing if any fail validation
30
+ @activities = worker.options.activities.each_with_object({}) do |act, hash|
31
+ # Class means create each time, instance means just call, definition
32
+ # does nothing special
33
+ defn = Activity::Definition.from_activity(act)
34
+ # Confirm name not in use
35
+ raise ArgumentError, "Multiple activities named #{defn.name}" if hash.key?(defn.name)
36
+
37
+ # Confirm executor is a known executor and let it initialize
38
+ executor = worker.options.activity_executors[defn.executor]
39
+ raise ArgumentError, "Unknown executor '#{defn.executor}'" if executor.nil?
40
+
41
+ executor.initialize_activity(defn)
42
+
43
+ hash[defn.name] = defn
44
+ end
45
+
46
+ # Need mutex for the rest of these
47
+ @running_activities_mutex = Mutex.new
48
+ @running_activities = {}
49
+ @running_activities_empty_condvar = ConditionVariable.new
50
+ end
51
+
52
+ def set_running_activity(task_token, activity)
53
+ @running_activities_mutex.synchronize do
54
+ @running_activities[task_token] = activity
55
+ end
56
+ end
57
+
58
+ def get_running_activity(task_token)
59
+ @running_activities_mutex.synchronize do
60
+ @running_activities[task_token]
61
+ end
62
+ end
63
+
64
+ def remove_running_activity(task_token)
65
+ @running_activities_mutex.synchronize do
66
+ @running_activities.delete(task_token)
67
+ @running_activities_empty_condvar.broadcast if @running_activities.empty?
68
+ end
69
+ end
70
+
71
+ def wait_all_complete
72
+ @running_activities_mutex.synchronize do
73
+ @running_activities_empty_condvar.wait(@running_activities_mutex) until @running_activities.empty?
74
+ end
75
+ end
76
+
77
+ def handle_task(task)
78
+ @scoped_logger.debug("Received activity task: #{task}") if LOG_TASKS
79
+ if !task.start.nil?
80
+ handle_start_task(task.task_token, task.start)
81
+ elsif !task.cancel.nil?
82
+ handle_cancel_task(task.task_token, task.cancel)
83
+ else
84
+ raise "Unrecognized activity task: #{task}"
85
+ end
86
+ end
87
+
88
+ def handle_start_task(task_token, start)
89
+ set_running_activity(task_token, nil)
90
+
91
+ # Find activity definition
92
+ defn = @activities[start.activity_type]
93
+ if defn.nil?
94
+ raise Error::ApplicationError.new(
95
+ "Activity #{start.activity_type} for workflow #{start.workflow_execution.workflow_id} " \
96
+ "is not registered on this worker, available activities: #{@activities.keys.sort.join(', ')}",
97
+ type: 'NotFoundError'
98
+ )
99
+ end
100
+
101
+ # Run everything else in the excecutor
102
+ executor = @worker.options.activity_executors[defn.executor]
103
+ executor.execute_activity(defn) do
104
+ # Set current executor
105
+ Activity::Context._current_executor = executor
106
+ # Execute with error handling
107
+ execute_activity(task_token, defn, start)
108
+ ensure
109
+ # Unset at the end
110
+ Activity::Context._current_executor = nil
111
+ end
112
+ rescue Exception => e # rubocop:disable Lint/RescueException We are intending to catch everything here
113
+ remove_running_activity(task_token)
114
+ @scoped_logger.warn("Failed starting activity #{start.activity_type}")
115
+ @scoped_logger.warn(e)
116
+
117
+ # We need to complete the activity task as failed, but this is on the
118
+ # hot path for polling, so we want to complete it in the background
119
+ begin
120
+ @bridge_worker.complete_activity_task_in_background(
121
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
122
+ task_token:,
123
+ result: Bridge::Api::ActivityResult::ActivityExecutionResult.new(
124
+ failed: Bridge::Api::ActivityResult::Failure.new(
125
+ # TODO(cretz): If failure conversion does slow failure
126
+ # encoding, it can gum up the system
127
+ failure: @worker.options.client.data_converter.to_failure(e)
128
+ )
129
+ )
130
+ )
131
+ )
132
+ rescue StandardError => e_inner
133
+ @scoped_logger.error("Failed building start failure to return for #{start.activity_type}")
134
+ @scoped_logger.error(e_inner)
135
+ end
136
+ end
137
+
138
+ def handle_cancel_task(task_token, cancel)
139
+ activity = get_running_activity(task_token)
140
+ if activity.nil?
141
+ @scoped_logger.warn("Cannot find activity to cancel for token #{task_token}")
142
+ return
143
+ end
144
+ activity._server_requested_cancel = true
145
+ _, cancel_proc = activity.cancellation
146
+ begin
147
+ cancel_proc.call(reason: cancel.reason.to_s)
148
+ rescue StandardError => e
149
+ @scoped_logger.warn("Failed cancelling activity #{activity.info.activity_type} \
150
+ with ID #{activity.info.activity_id}")
151
+ @scoped_logger.warn(e)
152
+ end
153
+ end
154
+
155
+ def execute_activity(task_token, defn, start)
156
+ # Build info
157
+ info = Activity::Info.new(
158
+ activity_id: start.activity_id,
159
+ activity_type: start.activity_type,
160
+ attempt: start.attempt,
161
+ current_attempt_scheduled_time: start.current_attempt_scheduled_time.to_time,
162
+ heartbeat_details: ProtoUtils.convert_from_payload_array(
163
+ @worker.options.client.data_converter,
164
+ start.heartbeat_details.to_ary
165
+ ),
166
+ heartbeat_timeout: start.heartbeat_timeout&.to_f,
167
+ local?: start.is_local,
168
+ schedule_to_close_timeout: start.schedule_to_close_timeout&.to_f,
169
+ scheduled_time: start.scheduled_time.to_time,
170
+ start_to_close_timeout: start.start_to_close_timeout&.to_f,
171
+ started_time: start.started_time.to_time,
172
+ task_queue: @worker.options.task_queue,
173
+ task_token:,
174
+ workflow_id: start.workflow_execution.workflow_id,
175
+ workflow_namespace: start.workflow_namespace,
176
+ workflow_run_id: start.workflow_execution.run_id,
177
+ workflow_type: start.workflow_type
178
+ ).freeze
179
+
180
+ # Build input
181
+ input = Temporalio::Worker::Interceptor::ExecuteActivityInput.new(
182
+ proc: defn.proc,
183
+ args: ProtoUtils.convert_from_payload_array(
184
+ @worker.options.client.data_converter,
185
+ start.input.to_ary
186
+ ),
187
+ headers: start.header_fields
188
+ )
189
+
190
+ # Run
191
+ activity = RunningActivity.new(
192
+ info:,
193
+ cancellation: Cancellation.new,
194
+ worker_shutdown_cancellation: @worker._worker_shutdown_cancellation,
195
+ payload_converter: @worker.options.client.data_converter.payload_converter,
196
+ logger: @scoped_logger
197
+ )
198
+ Activity::Context._current_executor&.set_activity_context(defn, activity)
199
+ set_running_activity(task_token, activity)
200
+ run_activity(activity, input)
201
+ rescue Exception => e # rubocop:disable Lint/RescueException We are intending to catch everything here
202
+ @scoped_logger.warn("Failed starting or sending completion for activity #{start.activity_type}")
203
+ @scoped_logger.warn(e)
204
+ # This means that the activity couldn't start or send completion (run
205
+ # handles its own errors).
206
+ begin
207
+ @bridge_worker.complete_activity_task(
208
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
209
+ task_token:,
210
+ result: Bridge::Api::ActivityResult::ActivityExecutionResult.new(
211
+ failed: Bridge::Api::ActivityResult::Failure.new(
212
+ failure: @worker.options.client.data_converter.to_failure(e)
213
+ )
214
+ )
215
+ )
216
+ )
217
+ rescue StandardError => e_inner
218
+ @scoped_logger.error("Failed sending failure for activity #{start.activity_type}")
219
+ @scoped_logger.error(e_inner)
220
+ end
221
+ ensure
222
+ Activity::Context._current_executor&.set_activity_context(defn, nil)
223
+ remove_running_activity(task_token)
224
+ end
225
+
226
+ def run_activity(activity, input)
227
+ result = begin
228
+ # Build impl with interceptors
229
+ # @type var impl: Temporalio::Worker::Interceptor::ActivityInbound
230
+ impl = InboundImplementation.new(self)
231
+ impl = @worker._all_interceptors.reverse_each.reduce(impl) do |acc, int|
232
+ int.intercept_activity(acc)
233
+ end
234
+ impl.init(OutboundImplementation.new(self))
235
+
236
+ # Execute
237
+ result = impl.execute(input)
238
+
239
+ # Success
240
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
241
+ completed: Bridge::Api::ActivityResult::Success.new(
242
+ result: @worker.options.client.data_converter.to_payload(result)
243
+ )
244
+ )
245
+ rescue Exception => e # rubocop:disable Lint/RescueException We are intending to catch everything here
246
+ if e.is_a?(Activity::CompleteAsyncError)
247
+ # Wanting to complete async
248
+ @scoped_logger.debug('Completing activity asynchronously')
249
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
250
+ will_complete_async: Bridge::Api::ActivityResult::WillCompleteAsync.new
251
+ )
252
+ elsif e.is_a?(Error::CanceledError) && activity._server_requested_cancel
253
+ # Server requested cancel
254
+ @scoped_logger.debug('Completing activity as canceled')
255
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
256
+ cancelled: Bridge::Api::ActivityResult::Cancellation.new(
257
+ failure: @worker.options.client.data_converter.to_failure(e)
258
+ )
259
+ )
260
+ else
261
+ # General failure
262
+ @scoped_logger.warn('Completing activity as failed')
263
+ @scoped_logger.warn(e)
264
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
265
+ failed: Bridge::Api::ActivityResult::Failure.new(
266
+ failure: @worker.options.client.data_converter.to_failure(e)
267
+ )
268
+ )
269
+ end
270
+ end
271
+
272
+ @scoped_logger.debug("Sending activity completion: #{result}") if LOG_TASKS
273
+ @bridge_worker.complete_activity_task(
274
+ Bridge::Api::CoreInterface::ActivityTaskCompletion.new(
275
+ task_token: activity.info.task_token,
276
+ result:
277
+ )
278
+ )
279
+ end
280
+
281
+ class RunningActivity < Activity::Context
282
+ attr_reader :info, :cancellation, :worker_shutdown_cancellation, :payload_converter, :logger
283
+ attr_accessor :_outbound_impl, :_server_requested_cancel
284
+
285
+ def initialize( # rubocop:disable Lint/MissingSuper
286
+ info:,
287
+ cancellation:,
288
+ worker_shutdown_cancellation:,
289
+ payload_converter:,
290
+ logger:
291
+ )
292
+ @info = info
293
+ @cancellation = cancellation
294
+ @worker_shutdown_cancellation = worker_shutdown_cancellation
295
+ @payload_converter = payload_converter
296
+ @logger = logger
297
+ @_outbound_impl = nil
298
+ @_server_requested_cancel = false
299
+ end
300
+
301
+ def heartbeat(*details)
302
+ raise 'Implementation not set yet' if _outbound_impl.nil?
303
+
304
+ _outbound_impl.heartbeat(Temporalio::Worker::Interceptor::HeartbeatActivityInput.new(details:))
305
+ end
306
+ end
307
+
308
+ class InboundImplementation < Temporalio::Worker::Interceptor::ActivityInbound
309
+ def initialize(worker)
310
+ super(nil) # steep:ignore
311
+ @worker = worker
312
+ end
313
+
314
+ def init(outbound)
315
+ context = Activity::Context.current
316
+ raise 'Unexpected context type' unless context.is_a?(RunningActivity)
317
+
318
+ context._outbound_impl = outbound
319
+ end
320
+
321
+ def execute(input)
322
+ input.proc.call(*input.args)
323
+ end
324
+ end
325
+
326
+ class OutboundImplementation < Temporalio::Worker::Interceptor::ActivityOutbound
327
+ def initialize(worker)
328
+ super(nil) # steep:ignore
329
+ @worker = worker
330
+ end
331
+
332
+ def heartbeat(input)
333
+ @worker.bridge_worker.record_activity_heartbeat(
334
+ Bridge::Api::CoreInterface::ActivityHeartbeat.new(
335
+ task_token: Activity::Context.current.info.task_token,
336
+ details: ProtoUtils.convert_to_payload_array(@worker.worker.options.client.data_converter,
337
+ input.details)
338
+ ).to_proto
339
+ )
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
@@ -0,0 +1,169 @@
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
+ class MultiRunner
10
+ def initialize(workers:, shutdown_signals:)
11
+ @workers = workers
12
+ @queue = Queue.new
13
+
14
+ @shutdown_initiated_mutex = Mutex.new
15
+ @shutdown_initiated = false
16
+
17
+ # Trap signals to push to queue
18
+ shutdown_signals.each do |signal|
19
+ Signal.trap(signal) { @queue.push(Event::ShutdownSignalReceived.new) }
20
+ end
21
+
22
+ # Start pollers
23
+ Bridge::Worker.async_poll_all(workers.map(&:_bridge_worker), @queue)
24
+ end
25
+
26
+ def apply_thread_or_fiber_block(&)
27
+ return unless block_given?
28
+
29
+ @thread_or_fiber = if Fiber.current_scheduler
30
+ Fiber.schedule do
31
+ @queue.push(Event::BlockSuccess.new(result: yield))
32
+ rescue InjectEventForTesting => e
33
+ @queue.push(e.event)
34
+ @queue.push(Event::BlockSuccess.new(result: e))
35
+ rescue Exception => e # rubocop:disable Lint/RescueException Intentionally catch all
36
+ @queue.push(Event::BlockFailure.new(error: e))
37
+ end
38
+ else
39
+ Thread.new do
40
+ @queue.push(Event::BlockSuccess.new(result: yield))
41
+ rescue InjectEventForTesting => e
42
+ @queue.push(e.event)
43
+ @queue.push(Event::BlockSuccess.new(result: e))
44
+ rescue Exception => e # rubocop:disable Lint/RescueException Intentionally catch all
45
+ @queue.push(Event::BlockFailure.new(error: e))
46
+ end
47
+ end
48
+ end
49
+
50
+ def raise_in_thread_or_fiber_block(error)
51
+ @thread_or_fiber&.raise(error)
52
+ end
53
+
54
+ # Clarify this is the only thread-safe function here
55
+ def initiate_shutdown
56
+ should_call = @shutdown_initiated_mutex.synchronize do
57
+ break false if @shutdown_initiated
58
+
59
+ @shutdown_initiated = true
60
+ end
61
+ return unless should_call
62
+
63
+ @workers.each(&:_initiate_shutdown)
64
+ end
65
+
66
+ def wait_complete_and_finalize_shutdown
67
+ # Wait for them all to complete
68
+ @workers.each(&:_wait_all_complete)
69
+
70
+ # Finalize them all
71
+ Bridge::Worker.finalize_shutdown_all(@workers.map(&:_bridge_worker))
72
+ end
73
+
74
+ # Intentionally not an enumerable/enumerator since stop semantics are
75
+ # caller determined
76
+ def next_event
77
+ # Queue value is one of the following:
78
+ # * Event - non-poller event
79
+ # * [worker index, :activity/:workflow, bytes] - poll success
80
+ # * [worker index, :activity/:workflow, error] - poll fail
81
+ # * [worker index, :activity/:workflow, nil] - worker shutdown
82
+ # * [nil, nil, nil] - all pollers done
83
+ result = @queue.pop
84
+ if result.is_a?(Event)
85
+ result
86
+ else
87
+ worker_index, worker_type, poll_result = result
88
+ if worker_index.nil? || worker_type.nil?
89
+ Event::AllPollersShutDown.instance
90
+ else
91
+ worker = @workers[worker_index]
92
+ case poll_result
93
+ when nil
94
+ Event::PollerShutDown.new(worker:, worker_type:)
95
+ when Exception
96
+ Event::PollFailure.new(worker:, worker_type:, error: poll_result)
97
+ else
98
+ Event::PollSuccess.new(worker:, worker_type:, bytes: poll_result)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ class Event
105
+ class PollSuccess < Event
106
+ attr_reader :worker, :worker_type, :bytes
107
+
108
+ def initialize(worker:, worker_type:, bytes:) # rubocop:disable Lint/MissingSuper
109
+ @worker = worker
110
+ @worker_type = worker_type
111
+ @bytes = bytes
112
+ end
113
+ end
114
+
115
+ class PollFailure < Event
116
+ attr_reader :worker, :worker_type, :error
117
+
118
+ def initialize(worker:, worker_type:, error:) # rubocop:disable Lint/MissingSuper
119
+ @worker = worker
120
+ @worker_type = worker_type
121
+ @error = error
122
+ end
123
+ end
124
+
125
+ class PollerShutDown < Event
126
+ attr_reader :worker, :worker_type
127
+
128
+ def initialize(worker:, worker_type:) # rubocop:disable Lint/MissingSuper
129
+ @worker = worker
130
+ @worker_type = worker_type
131
+ end
132
+ end
133
+
134
+ class AllPollersShutDown < Event
135
+ include Singleton
136
+ end
137
+
138
+ class BlockSuccess < Event
139
+ attr_reader :result
140
+
141
+ def initialize(result:) # rubocop:disable Lint/MissingSuper
142
+ @result = result
143
+ end
144
+ end
145
+
146
+ class BlockFailure < Event
147
+ attr_reader :error
148
+
149
+ def initialize(error:) # rubocop:disable Lint/MissingSuper
150
+ @error = error
151
+ end
152
+ end
153
+
154
+ class ShutdownSignalReceived < Event
155
+ end
156
+ end
157
+
158
+ class InjectEventForTesting < Temporalio::Error
159
+ attr_reader :event
160
+
161
+ def initialize(event)
162
+ super('Injecting event for testing')
163
+ @event = event
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ # @!visibility private
5
+ module Internal
6
+ end
7
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/internal/proto_utils'
4
+
5
+ module Temporalio
6
+ # Options for retrying workflows and activities.
7
+ #
8
+ # @!attribute initial_interval
9
+ # @return [Float] Backoff interval in seconds for the first retry. Default 1.0.
10
+ # @!attribute backoff_coefficient
11
+ # @return [Float] Coefficient to multiply previous backoff interval by to get new interval. Default 2.0.
12
+ # @!attribute max_interval
13
+ # @return [Float, nil] Maximum backoff interval in seconds between retries. Default 100x `initial_interval`.
14
+ # @!attribute max_attempts
15
+ # @return [Integer] Maximum number of attempts. If `0`, the default, there is no maximum.
16
+ # @!attribute non_retryable_error_types
17
+ # @return [Array<String>, nil] List of error types that are not retryable.
18
+ RetryPolicy = Struct.new(
19
+ :initial_interval,
20
+ :backoff_coefficient,
21
+ :max_interval,
22
+ :max_attempts,
23
+ :non_retryable_error_types,
24
+ keyword_init: true
25
+ ) do
26
+ def initialize(*, **kwargs)
27
+ kwargs[:initial_interval] = 1.0 unless kwargs.key?(:initial_interval)
28
+ kwargs[:backoff_coefficient] = 2.0 unless kwargs.key?(:backoff_coefficient)
29
+ kwargs[:max_attempts] = 0 unless kwargs.key?(:max_attempts)
30
+ super
31
+ end
32
+
33
+ # @!visibility private
34
+ def to_proto
35
+ # @type self: RetryPolicy
36
+ raise 'Initial interval cannot be negative' if initial_interval.negative?
37
+ raise 'Backoff coefficient cannot be less than 1' if backoff_coefficient < 1
38
+ raise 'Max interval cannot be negative' if max_interval&.negative?
39
+ raise 'Max interval cannot be less than initial interval' if max_interval && max_interval < initial_interval
40
+ raise 'Max attempts cannot be negative' if max_attempts.negative?
41
+
42
+ Api::Common::V1::RetryPolicy.new(
43
+ initial_interval: Internal::ProtoUtils.seconds_to_duration(initial_interval),
44
+ backoff_coefficient:,
45
+ maximum_interval: Internal::ProtoUtils.seconds_to_duration(max_interval),
46
+ maximum_attempts: max_attempts,
47
+ non_retryable_error_types:
48
+ )
49
+ end
50
+ end
51
+ end