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.
- checksums.yaml +7 -0
- data/Gemfile +23 -0
- data/Rakefile +387 -0
- data/lib/temporalio/activity/complete_async_error.rb +11 -0
- data/lib/temporalio/activity/context.rb +107 -0
- data/lib/temporalio/activity/definition.rb +77 -0
- data/lib/temporalio/activity/info.rb +63 -0
- data/lib/temporalio/activity.rb +69 -0
- data/lib/temporalio/api/batch/v1/message.rb +31 -0
- data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +93 -0
- data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
- data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
- data/lib/temporalio/api/cloud/identity/v1/message.rb +36 -0
- data/lib/temporalio/api/cloud/namespace/v1/message.rb +35 -0
- data/lib/temporalio/api/cloud/operation/v1/message.rb +27 -0
- data/lib/temporalio/api/cloud/region/v1/message.rb +23 -0
- data/lib/temporalio/api/command/v1/message.rb +46 -0
- data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
- data/lib/temporalio/api/common/v1/message.rb +41 -0
- data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
- data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
- data/lib/temporalio/api/enums/v1/common.rb +26 -0
- data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
- data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
- data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
- data/lib/temporalio/api/enums/v1/query.rb +22 -0
- data/lib/temporalio/api/enums/v1/reset.rb +23 -0
- data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
- data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
- data/lib/temporalio/api/enums/v1/update.rb +22 -0
- data/lib/temporalio/api/enums/v1/workflow.rb +30 -0
- data/lib/temporalio/api/errordetails/v1/message.rb +42 -0
- data/lib/temporalio/api/export/v1/message.rb +24 -0
- data/lib/temporalio/api/failure/v1/message.rb +35 -0
- data/lib/temporalio/api/filter/v1/message.rb +27 -0
- data/lib/temporalio/api/history/v1/message.rb +90 -0
- data/lib/temporalio/api/namespace/v1/message.rb +31 -0
- data/lib/temporalio/api/nexus/v1/message.rb +40 -0
- data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
- data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
- data/lib/temporalio/api/operatorservice.rb +3 -0
- data/lib/temporalio/api/protocol/v1/message.rb +23 -0
- data/lib/temporalio/api/query/v1/message.rb +27 -0
- data/lib/temporalio/api/replication/v1/message.rb +26 -0
- data/lib/temporalio/api/schedule/v1/message.rb +42 -0
- data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +25 -0
- data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +21 -0
- data/lib/temporalio/api/sdk/v1/user_metadata.rb +23 -0
- data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +23 -0
- data/lib/temporalio/api/taskqueue/v1/message.rb +45 -0
- data/lib/temporalio/api/update/v1/message.rb +33 -0
- data/lib/temporalio/api/version/v1/message.rb +26 -0
- data/lib/temporalio/api/workflow/v1/message.rb +43 -0
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +189 -0
- data/lib/temporalio/api/workflowservice/v1/service.rb +23 -0
- data/lib/temporalio/api/workflowservice.rb +3 -0
- data/lib/temporalio/api.rb +13 -0
- data/lib/temporalio/cancellation.rb +150 -0
- data/lib/temporalio/client/activity_id_reference.rb +32 -0
- data/lib/temporalio/client/async_activity_handle.rb +110 -0
- data/lib/temporalio/client/connection/cloud_service.rb +648 -0
- data/lib/temporalio/client/connection/operator_service.rb +249 -0
- data/lib/temporalio/client/connection/service.rb +41 -0
- data/lib/temporalio/client/connection/workflow_service.rb +1218 -0
- data/lib/temporalio/client/connection.rb +270 -0
- data/lib/temporalio/client/interceptor.rb +316 -0
- data/lib/temporalio/client/workflow_execution.rb +103 -0
- data/lib/temporalio/client/workflow_execution_count.rb +36 -0
- data/lib/temporalio/client/workflow_execution_status.rb +18 -0
- data/lib/temporalio/client/workflow_handle.rb +446 -0
- data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
- data/lib/temporalio/client/workflow_update_handle.rb +67 -0
- data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
- data/lib/temporalio/client.rb +404 -0
- data/lib/temporalio/common_enums.rb +24 -0
- data/lib/temporalio/converters/data_converter.rb +102 -0
- data/lib/temporalio/converters/failure_converter.rb +200 -0
- data/lib/temporalio/converters/payload_codec.rb +26 -0
- data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
- data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
- data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
- data/lib/temporalio/converters/payload_converter/composite.rb +62 -0
- data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
- data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
- data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
- data/lib/temporalio/converters/payload_converter.rb +73 -0
- data/lib/temporalio/converters.rb +9 -0
- data/lib/temporalio/error/failure.rb +219 -0
- data/lib/temporalio/error.rb +147 -0
- data/lib/temporalio/internal/bridge/3.1/temporalio_bridge.so +0 -0
- data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
- data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
- data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
- data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +31 -0
- data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
- data/lib/temporalio/internal/bridge/api/common/common.rb +26 -0
- data/lib/temporalio/internal/bridge/api/core_interface.rb +36 -0
- data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +52 -0
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +54 -0
- data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +30 -0
- data/lib/temporalio/internal/bridge/api.rb +3 -0
- data/lib/temporalio/internal/bridge/client.rb +90 -0
- data/lib/temporalio/internal/bridge/runtime.rb +53 -0
- data/lib/temporalio/internal/bridge/testing.rb +46 -0
- data/lib/temporalio/internal/bridge/worker.rb +83 -0
- data/lib/temporalio/internal/bridge.rb +36 -0
- data/lib/temporalio/internal/client/implementation.rb +525 -0
- data/lib/temporalio/internal/proto_utils.rb +54 -0
- data/lib/temporalio/internal/worker/activity_worker.rb +345 -0
- data/lib/temporalio/internal/worker/multi_runner.rb +169 -0
- data/lib/temporalio/internal.rb +7 -0
- data/lib/temporalio/retry_policy.rb +51 -0
- data/lib/temporalio/runtime.rb +271 -0
- data/lib/temporalio/scoped_logger.rb +96 -0
- data/lib/temporalio/search_attributes.rb +300 -0
- data/lib/temporalio/testing/activity_environment.rb +132 -0
- data/lib/temporalio/testing/workflow_environment.rb +137 -0
- data/lib/temporalio/testing.rb +10 -0
- data/lib/temporalio/version.rb +5 -0
- data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
- data/lib/temporalio/worker/activity_executor/thread_pool.rb +254 -0
- data/lib/temporalio/worker/activity_executor.rb +55 -0
- data/lib/temporalio/worker/interceptor.rb +88 -0
- data/lib/temporalio/worker/tuner.rb +151 -0
- data/lib/temporalio/worker.rb +426 -0
- data/lib/temporalio/workflow_history.rb +22 -0
- data/lib/temporalio.rb +7 -0
- data/temporalio.gemspec +28 -0
- 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,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
|