temporalio 0.2.0-x86_64-darwin → 0.3.0-x86_64-darwin
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 +4 -4
- data/.yardopts +2 -0
- data/Gemfile +3 -3
- data/Rakefile +10 -296
- data/lib/temporalio/activity/complete_async_error.rb +1 -1
- data/lib/temporalio/activity/context.rb +5 -2
- data/lib/temporalio/activity/definition.rb +163 -65
- data/lib/temporalio/activity/info.rb +22 -21
- data/lib/temporalio/activity.rb +2 -59
- data/lib/temporalio/api/activity/v1/message.rb +25 -0
- data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
- data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
- data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
- data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
- data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
- data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
- data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
- data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
- data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
- data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
- data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
- data/lib/temporalio/api/common/v1/message.rb +7 -1
- data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
- data/lib/temporalio/api/enums/v1/reset.rb +1 -1
- data/lib/temporalio/api/history/v1/message.rb +1 -1
- data/lib/temporalio/api/nexus/v1/message.rb +2 -2
- data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
- data/lib/temporalio/api/payload_visitor.rb +1513 -0
- data/lib/temporalio/api/schedule/v1/message.rb +2 -1
- data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
- data/lib/temporalio/api/testservice/v1/service.rb +23 -0
- data/lib/temporalio/api/workflow/v1/message.rb +1 -1
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +17 -2
- data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/cancellation.rb +34 -14
- data/lib/temporalio/client/async_activity_handle.rb +12 -37
- data/lib/temporalio/client/connection/cloud_service.rb +309 -231
- data/lib/temporalio/client/connection/operator_service.rb +36 -84
- data/lib/temporalio/client/connection/service.rb +6 -5
- data/lib/temporalio/client/connection/test_service.rb +111 -0
- data/lib/temporalio/client/connection/workflow_service.rb +264 -441
- data/lib/temporalio/client/connection.rb +90 -44
- data/lib/temporalio/client/interceptor.rb +160 -60
- data/lib/temporalio/client/schedule.rb +967 -0
- data/lib/temporalio/client/schedule_handle.rb +126 -0
- data/lib/temporalio/client/workflow_execution.rb +7 -10
- data/lib/temporalio/client/workflow_handle.rb +38 -95
- data/lib/temporalio/client/workflow_update_handle.rb +3 -5
- data/lib/temporalio/client.rb +122 -42
- data/lib/temporalio/common_enums.rb +17 -0
- data/lib/temporalio/converters/data_converter.rb +4 -7
- data/lib/temporalio/converters/failure_converter.rb +5 -3
- data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
- data/lib/temporalio/converters/payload_converter.rb +6 -8
- data/lib/temporalio/converters/raw_value.rb +20 -0
- data/lib/temporalio/error/failure.rb +1 -1
- data/lib/temporalio/error.rb +10 -2
- data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/{3.1 → 3.4}/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
- data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
- data/lib/temporalio/internal/bridge/client.rb +11 -6
- data/lib/temporalio/internal/bridge/testing.rb +20 -0
- data/lib/temporalio/internal/bridge/worker.rb +2 -0
- data/lib/temporalio/internal/bridge.rb +1 -1
- data/lib/temporalio/internal/client/implementation.rb +245 -70
- data/lib/temporalio/internal/metric.rb +122 -0
- data/lib/temporalio/internal/proto_utils.rb +86 -7
- data/lib/temporalio/internal/worker/activity_worker.rb +52 -24
- data/lib/temporalio/internal/worker/multi_runner.rb +51 -7
- data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +329 -0
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +44 -0
- data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
- data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
- data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
- data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
- data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
- data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +415 -0
- data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
- data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +163 -0
- data/lib/temporalio/internal/worker/workflow_instance.rb +730 -0
- data/lib/temporalio/internal/worker/workflow_worker.rb +196 -0
- data/lib/temporalio/metric.rb +109 -0
- data/lib/temporalio/retry_policy.rb +37 -14
- data/lib/temporalio/runtime.rb +118 -75
- data/lib/temporalio/search_attributes.rb +80 -37
- data/lib/temporalio/testing/activity_environment.rb +2 -2
- data/lib/temporalio/testing/workflow_environment.rb +251 -5
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
- data/lib/temporalio/worker/activity_executor.rb +3 -3
- data/lib/temporalio/worker/interceptor.rb +340 -66
- data/lib/temporalio/worker/thread_pool.rb +237 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +230 -0
- data/lib/temporalio/worker/workflow_executor.rb +26 -0
- data/lib/temporalio/worker.rb +201 -30
- data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
- data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
- data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
- data/lib/temporalio/workflow/definition.rb +566 -0
- data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
- data/lib/temporalio/workflow/future.rb +151 -0
- data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
- data/lib/temporalio/workflow/info.rb +82 -0
- data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
- data/lib/temporalio/workflow/update_info.rb +20 -0
- data/lib/temporalio/workflow.rb +523 -0
- data/lib/temporalio.rb +4 -0
- data/temporalio.gemspec +2 -2
- metadata +52 -6
@@ -0,0 +1,415 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'temporalio/activity/definition'
|
4
|
+
require 'temporalio/cancellation'
|
5
|
+
require 'temporalio/error'
|
6
|
+
require 'temporalio/internal/bridge/api'
|
7
|
+
require 'temporalio/internal/proto_utils'
|
8
|
+
require 'temporalio/internal/worker/workflow_instance'
|
9
|
+
require 'temporalio/worker/interceptor'
|
10
|
+
require 'temporalio/workflow'
|
11
|
+
require 'temporalio/workflow/child_workflow_handle'
|
12
|
+
|
13
|
+
module Temporalio
|
14
|
+
module Internal
|
15
|
+
module Worker
|
16
|
+
class WorkflowInstance
|
17
|
+
# Root implementation of the outbound interceptor.
|
18
|
+
class OutboundImplementation < Temporalio::Worker::Interceptor::Workflow::Outbound
|
19
|
+
def initialize(instance)
|
20
|
+
super(nil) # steep:ignore
|
21
|
+
@instance = instance
|
22
|
+
@activity_counter = 0
|
23
|
+
@timer_counter = 0
|
24
|
+
@child_counter = 0
|
25
|
+
@external_signal_counter = 0
|
26
|
+
@external_cancel_counter = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def cancel_external_workflow(input)
|
30
|
+
# Add command
|
31
|
+
seq = (@external_cancel_counter += 1)
|
32
|
+
cmd = Bridge::Api::WorkflowCommands::RequestCancelExternalWorkflowExecution.new(
|
33
|
+
seq:,
|
34
|
+
workflow_execution: Bridge::Api::Common::NamespacedWorkflowExecution.new(
|
35
|
+
namespace: @instance.info.namespace,
|
36
|
+
workflow_id: input.id,
|
37
|
+
run_id: input.run_id
|
38
|
+
)
|
39
|
+
)
|
40
|
+
@instance.add_command(
|
41
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(request_cancel_external_workflow_execution: cmd)
|
42
|
+
)
|
43
|
+
@instance.pending_external_cancels[seq] = Fiber.current
|
44
|
+
|
45
|
+
# Wait
|
46
|
+
resolution = Fiber.yield
|
47
|
+
|
48
|
+
# Raise if resolution has failure
|
49
|
+
return unless resolution.failure
|
50
|
+
|
51
|
+
raise @instance.failure_converter.from_failure(resolution.failure, @instance.payload_converter)
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute_activity(input)
|
55
|
+
if input.schedule_to_close_timeout.nil? && input.start_to_close_timeout.nil?
|
56
|
+
raise ArgumentError, 'Activity must have schedule_to_close_timeout or start_to_close_timeout'
|
57
|
+
end
|
58
|
+
|
59
|
+
activity_type = case input.activity
|
60
|
+
when Class
|
61
|
+
Activity::Definition::Info.from_activity(input.activity).name
|
62
|
+
when Symbol, String
|
63
|
+
input.activity.to_s
|
64
|
+
else
|
65
|
+
raise ArgumentError, 'Activity must be a definition class, or a symbol/string'
|
66
|
+
end
|
67
|
+
raise 'Cannot invoke dynamic activities' unless activity_type
|
68
|
+
|
69
|
+
execute_activity_with_local_backoffs(local: false, cancellation: input.cancellation) do
|
70
|
+
seq = (@activity_counter += 1)
|
71
|
+
@instance.add_command(
|
72
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
73
|
+
schedule_activity: Bridge::Api::WorkflowCommands::ScheduleActivity.new(
|
74
|
+
seq:,
|
75
|
+
activity_id: input.activity_id || seq.to_s,
|
76
|
+
activity_type:,
|
77
|
+
task_queue: input.task_queue,
|
78
|
+
headers: ProtoUtils.headers_to_proto_hash(input.headers, @instance.payload_converter),
|
79
|
+
arguments: ProtoUtils.convert_to_payload_array(@instance.payload_converter, input.args),
|
80
|
+
schedule_to_close_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_close_timeout),
|
81
|
+
schedule_to_start_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_start_timeout),
|
82
|
+
start_to_close_timeout: ProtoUtils.seconds_to_duration(input.start_to_close_timeout),
|
83
|
+
heartbeat_timeout: ProtoUtils.seconds_to_duration(input.heartbeat_timeout),
|
84
|
+
retry_policy: input.retry_policy&._to_proto,
|
85
|
+
cancellation_type: input.cancellation_type,
|
86
|
+
do_not_eagerly_execute: input.disable_eager_execution
|
87
|
+
)
|
88
|
+
)
|
89
|
+
)
|
90
|
+
seq
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def execute_local_activity(input)
|
95
|
+
if input.schedule_to_close_timeout.nil? && input.start_to_close_timeout.nil?
|
96
|
+
raise ArgumentError, 'Activity must have schedule_to_close_timeout or start_to_close_timeout'
|
97
|
+
end
|
98
|
+
|
99
|
+
activity_type = case input.activity
|
100
|
+
when Class
|
101
|
+
Activity::Definition::Info.from_activity(input.activity).name
|
102
|
+
when Symbol, String
|
103
|
+
input.activity.to_s
|
104
|
+
else
|
105
|
+
raise ArgumentError, 'Activity must be a definition class, or a symbol/string'
|
106
|
+
end
|
107
|
+
raise 'Cannot invoke dynamic activities' unless activity_type
|
108
|
+
|
109
|
+
execute_activity_with_local_backoffs(local: true, cancellation: input.cancellation) do |do_backoff|
|
110
|
+
seq = (@activity_counter += 1)
|
111
|
+
@instance.add_command(
|
112
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
113
|
+
schedule_local_activity: Bridge::Api::WorkflowCommands::ScheduleLocalActivity.new(
|
114
|
+
seq:,
|
115
|
+
activity_id: input.activity_id || seq.to_s,
|
116
|
+
activity_type:,
|
117
|
+
headers: ProtoUtils.headers_to_proto_hash(input.headers, @instance.payload_converter),
|
118
|
+
arguments: ProtoUtils.convert_to_payload_array(@instance.payload_converter, input.args),
|
119
|
+
schedule_to_close_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_close_timeout),
|
120
|
+
schedule_to_start_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_start_timeout),
|
121
|
+
start_to_close_timeout: ProtoUtils.seconds_to_duration(input.start_to_close_timeout),
|
122
|
+
retry_policy: input.retry_policy&._to_proto,
|
123
|
+
cancellation_type: input.cancellation_type,
|
124
|
+
local_retry_threshold: ProtoUtils.seconds_to_duration(input.local_retry_threshold),
|
125
|
+
attempt: do_backoff&.attempt || 0,
|
126
|
+
original_schedule_time: do_backoff&.original_schedule_time
|
127
|
+
)
|
128
|
+
)
|
129
|
+
)
|
130
|
+
seq
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def execute_activity_with_local_backoffs(local:, cancellation:, &)
|
135
|
+
# We do not even want to schedule if the cancellation is already cancelled. We choose to use canceled
|
136
|
+
# failure instead of wrapping in activity failure which is similar to what other SDKs do, with the accepted
|
137
|
+
# tradeoff that it makes rescue more difficult (hence the presence of Error.canceled? helper).
|
138
|
+
raise Error::CanceledError, 'Activity canceled before scheduled' if cancellation.canceled?
|
139
|
+
|
140
|
+
# This has to be done in a loop for local activity backoff
|
141
|
+
last_local_backoff = nil
|
142
|
+
loop do
|
143
|
+
result = execute_activity_once(local:, cancellation:, last_local_backoff:, &)
|
144
|
+
return result unless result.is_a?(Bridge::Api::ActivityResult::DoBackoff)
|
145
|
+
|
146
|
+
# @type var result: untyped
|
147
|
+
last_local_backoff = result
|
148
|
+
# Have to sleep the amount of the backoff, which can be canceled with the same cancellation
|
149
|
+
# TODO(cretz): What should this cancellation raise?
|
150
|
+
Workflow.sleep(ProtoUtils.duration_to_seconds(result.backoff_duration), cancellation:)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# If this doesn't raise, it returns success | DoBackoff
|
155
|
+
def execute_activity_once(local:, cancellation:, last_local_backoff:, &)
|
156
|
+
# Add to pending activities (removed by the resolver)
|
157
|
+
seq = yield last_local_backoff
|
158
|
+
@instance.pending_activities[seq] = Fiber.current
|
159
|
+
|
160
|
+
# Add cancellation hook
|
161
|
+
cancel_callback_key = cancellation.add_cancel_callback do
|
162
|
+
# Only if the activity is present still
|
163
|
+
if @instance.pending_activities.include?(seq)
|
164
|
+
if local
|
165
|
+
@instance.add_command(
|
166
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
167
|
+
request_cancel_local_activity: Bridge::Api::WorkflowCommands::RequestCancelLocalActivity.new(seq:)
|
168
|
+
)
|
169
|
+
)
|
170
|
+
else
|
171
|
+
@instance.add_command(
|
172
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
173
|
+
request_cancel_activity: Bridge::Api::WorkflowCommands::RequestCancelActivity.new(seq:)
|
174
|
+
)
|
175
|
+
)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Wait
|
181
|
+
resolution = Fiber.yield
|
182
|
+
|
183
|
+
# Remove cancellation callback
|
184
|
+
cancellation.remove_cancel_callback(cancel_callback_key)
|
185
|
+
|
186
|
+
case resolution.status
|
187
|
+
when :completed
|
188
|
+
@instance.payload_converter.from_payload(resolution.completed.result)
|
189
|
+
when :failed
|
190
|
+
raise @instance.failure_converter.from_failure(resolution.failed.failure, @instance.payload_converter)
|
191
|
+
when :cancelled
|
192
|
+
raise @instance.failure_converter.from_failure(resolution.cancelled.failure, @instance.payload_converter)
|
193
|
+
when :backoff
|
194
|
+
resolution.backoff
|
195
|
+
else
|
196
|
+
raise "Unrecognized resolution status: #{resolution.status}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def initialize_continue_as_new_error(input)
|
201
|
+
# Do nothing
|
202
|
+
end
|
203
|
+
|
204
|
+
def signal_child_workflow(input)
|
205
|
+
_signal_external_workflow(
|
206
|
+
id: input.id,
|
207
|
+
run_id: nil,
|
208
|
+
child: true,
|
209
|
+
signal: input.signal,
|
210
|
+
args: input.args,
|
211
|
+
cancellation: input.cancellation,
|
212
|
+
headers: input.headers
|
213
|
+
)
|
214
|
+
end
|
215
|
+
|
216
|
+
def signal_external_workflow(input)
|
217
|
+
_signal_external_workflow(
|
218
|
+
id: input.id,
|
219
|
+
run_id: input.run_id,
|
220
|
+
child: false,
|
221
|
+
signal: input.signal,
|
222
|
+
args: input.args,
|
223
|
+
cancellation: input.cancellation,
|
224
|
+
headers: input.headers
|
225
|
+
)
|
226
|
+
end
|
227
|
+
|
228
|
+
def _signal_external_workflow(id:, run_id:, child:, signal:, args:, cancellation:, headers:)
|
229
|
+
raise Error::CanceledError, 'Signal canceled before scheduled' if cancellation.canceled?
|
230
|
+
|
231
|
+
# Add command
|
232
|
+
seq = (@external_signal_counter += 1)
|
233
|
+
cmd = Bridge::Api::WorkflowCommands::SignalExternalWorkflowExecution.new(
|
234
|
+
seq:,
|
235
|
+
signal_name: Workflow::Definition::Signal._name_from_parameter(signal),
|
236
|
+
args: ProtoUtils.convert_to_payload_array(@instance.payload_converter, args),
|
237
|
+
headers: ProtoUtils.headers_to_proto_hash(headers, @instance.payload_converter)
|
238
|
+
)
|
239
|
+
if child
|
240
|
+
cmd.child_workflow_id = id
|
241
|
+
else
|
242
|
+
cmd.workflow_execution = Bridge::Api::Common::NamespacedWorkflowExecution.new(
|
243
|
+
namespace: @instance.info.namespace,
|
244
|
+
workflow_id: id,
|
245
|
+
run_id:
|
246
|
+
)
|
247
|
+
end
|
248
|
+
@instance.add_command(
|
249
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(signal_external_workflow_execution: cmd)
|
250
|
+
)
|
251
|
+
@instance.pending_external_signals[seq] = Fiber.current
|
252
|
+
|
253
|
+
# Add a cancellation callback
|
254
|
+
cancel_callback_key = cancellation.add_cancel_callback do
|
255
|
+
# Add the command but do not raise, we will let resolution do that
|
256
|
+
@instance.add_command(
|
257
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
258
|
+
cancel_signal_workflow: Bridge::Api::WorkflowCommands::CancelSignalWorkflow.new(seq:)
|
259
|
+
)
|
260
|
+
)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Wait
|
264
|
+
resolution = Fiber.yield
|
265
|
+
|
266
|
+
# Remove cancellation callback
|
267
|
+
cancellation.remove_cancel_callback(cancel_callback_key)
|
268
|
+
|
269
|
+
# Raise if resolution has failure
|
270
|
+
return unless resolution.failure
|
271
|
+
|
272
|
+
raise @instance.failure_converter.from_failure(resolution.failure, @instance.payload_converter)
|
273
|
+
end
|
274
|
+
|
275
|
+
def sleep(input)
|
276
|
+
# If already cancelled, raise as such
|
277
|
+
if input.cancellation.canceled?
|
278
|
+
raise Error::CanceledError,
|
279
|
+
input.cancellation.canceled_reason || 'Timer canceled before started'
|
280
|
+
end
|
281
|
+
|
282
|
+
# Disallow negative durations
|
283
|
+
raise ArgumentError, 'Sleep duration cannot be less than 0' if input.duration&.negative?
|
284
|
+
|
285
|
+
# If the duration is infinite, just wait for cancellation
|
286
|
+
if input.duration.nil?
|
287
|
+
input.cancellation.wait
|
288
|
+
raise Error::CanceledError, input.cancellation.canceled_reason || 'Timer canceled'
|
289
|
+
end
|
290
|
+
|
291
|
+
# If duration is zero, we make it one millisecond. It was decided a 0 duration still makes a timer to ensure
|
292
|
+
# determinism if a timer's duration is altered from non-zero to zero or vice versa.
|
293
|
+
duration = input.duration
|
294
|
+
duration = 0.001 if duration.zero?
|
295
|
+
|
296
|
+
# Add command
|
297
|
+
seq = (@timer_counter += 1)
|
298
|
+
@instance.add_command(
|
299
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
300
|
+
start_timer: Bridge::Api::WorkflowCommands::StartTimer.new(
|
301
|
+
seq:,
|
302
|
+
start_to_fire_timeout: ProtoUtils.seconds_to_duration(duration)
|
303
|
+
)
|
304
|
+
)
|
305
|
+
)
|
306
|
+
@instance.pending_timers[seq] = Fiber.current
|
307
|
+
|
308
|
+
# Add a cancellation callback
|
309
|
+
cancel_callback_key = input.cancellation.add_cancel_callback do
|
310
|
+
# Only if the timer is still present
|
311
|
+
fiber = @instance.pending_timers.delete(seq)
|
312
|
+
if fiber
|
313
|
+
# Add the command for cancel then raise
|
314
|
+
@instance.add_command(
|
315
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
316
|
+
cancel_timer: Bridge::Api::WorkflowCommands::CancelTimer.new(seq:)
|
317
|
+
)
|
318
|
+
)
|
319
|
+
if fiber.alive?
|
320
|
+
fiber.raise(Error::CanceledError.new(input.cancellation.canceled_reason || 'Timer canceled'))
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Wait
|
326
|
+
Fiber.yield
|
327
|
+
|
328
|
+
# Remove cancellation callback (only needed on success)
|
329
|
+
input.cancellation.remove_cancel_callback(cancel_callback_key)
|
330
|
+
end
|
331
|
+
|
332
|
+
def start_child_workflow(input)
|
333
|
+
raise Error::CanceledError, 'Child canceled before scheduled' if input.cancellation.canceled?
|
334
|
+
|
335
|
+
# Add the command
|
336
|
+
seq = (@child_counter += 1)
|
337
|
+
@instance.add_command(
|
338
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
339
|
+
start_child_workflow_execution: Bridge::Api::WorkflowCommands::StartChildWorkflowExecution.new(
|
340
|
+
seq:,
|
341
|
+
namespace: @instance.info.namespace,
|
342
|
+
workflow_id: input.id,
|
343
|
+
workflow_type: Workflow::Definition._workflow_type_from_workflow_parameter(input.workflow),
|
344
|
+
task_queue: input.task_queue,
|
345
|
+
input: ProtoUtils.convert_to_payload_array(@instance.payload_converter, input.args),
|
346
|
+
workflow_execution_timeout: ProtoUtils.seconds_to_duration(input.execution_timeout),
|
347
|
+
workflow_run_timeout: ProtoUtils.seconds_to_duration(input.run_timeout),
|
348
|
+
workflow_task_timeout: ProtoUtils.seconds_to_duration(input.task_timeout),
|
349
|
+
parent_close_policy: input.parent_close_policy,
|
350
|
+
workflow_id_reuse_policy: input.id_reuse_policy,
|
351
|
+
retry_policy: input.retry_policy&._to_proto,
|
352
|
+
cron_schedule: input.cron_schedule,
|
353
|
+
headers: ProtoUtils.headers_to_proto_hash(input.headers, @instance.payload_converter),
|
354
|
+
memo: ProtoUtils.memo_to_proto_hash(input.memo, @instance.payload_converter),
|
355
|
+
search_attributes: input.search_attributes&._to_proto_hash,
|
356
|
+
cancellation_type: input.cancellation_type
|
357
|
+
)
|
358
|
+
)
|
359
|
+
)
|
360
|
+
|
361
|
+
# Set as pending start and register cancel callback
|
362
|
+
@instance.pending_child_workflow_starts[seq] = Fiber.current
|
363
|
+
cancel_callback_key = input.cancellation.add_cancel_callback do
|
364
|
+
# Send cancel if in start or pending
|
365
|
+
if @instance.pending_child_workflow_starts.include?(seq) ||
|
366
|
+
@instance.pending_child_workflows.include?(seq)
|
367
|
+
@instance.add_command(
|
368
|
+
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
369
|
+
cancel_child_workflow_execution: Bridge::Api::WorkflowCommands::CancelChildWorkflowExecution.new(
|
370
|
+
child_workflow_seq: seq
|
371
|
+
)
|
372
|
+
)
|
373
|
+
)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Wait for start
|
378
|
+
resolution = Fiber.yield
|
379
|
+
|
380
|
+
case resolution.status
|
381
|
+
when :succeeded
|
382
|
+
# Create handle, passing along the cancel callback key, and set it as pending
|
383
|
+
handle = ChildWorkflowHandle.new(
|
384
|
+
id: input.id,
|
385
|
+
first_execution_run_id: resolution.succeeded.run_id,
|
386
|
+
instance: @instance,
|
387
|
+
cancellation: input.cancellation,
|
388
|
+
cancel_callback_key:
|
389
|
+
)
|
390
|
+
@instance.pending_child_workflows[seq] = handle
|
391
|
+
handle
|
392
|
+
when :failed
|
393
|
+
# Remove cancel callback and handle failure
|
394
|
+
input.cancellation.remove_cancel_callback(cancel_callback_key)
|
395
|
+
if resolution.failed.cause == :START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_EXISTS
|
396
|
+
raise Error::WorkflowAlreadyStartedError.new(
|
397
|
+
workflow_id: resolution.failed.workflow_id,
|
398
|
+
workflow_type: resolution.failed.workflow_type,
|
399
|
+
run_id: nil
|
400
|
+
)
|
401
|
+
end
|
402
|
+
raise "Unknown child start fail cause: #{resolution.failed.cause}"
|
403
|
+
when :cancelled
|
404
|
+
# Remove cancel callback and handle cancel
|
405
|
+
input.cancellation.remove_cancel_callback(cancel_callback_key)
|
406
|
+
raise @instance.failure_converter.from_failure(resolution.cancelled.failure, @instance.payload_converter)
|
407
|
+
else
|
408
|
+
raise "Unknown resolution status: #{resolution.status}"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'temporalio/scoped_logger'
|
4
|
+
require 'temporalio/workflow'
|
5
|
+
|
6
|
+
module Temporalio
|
7
|
+
module Internal
|
8
|
+
module Worker
|
9
|
+
class WorkflowInstance
|
10
|
+
# Wrapper for a scoped logger that does not log on replay.
|
11
|
+
class ReplaySafeLogger < ScopedLogger
|
12
|
+
def initialize(logger:, instance:)
|
13
|
+
@instance = instance
|
14
|
+
@replay_safety_disabled = false
|
15
|
+
super(logger)
|
16
|
+
end
|
17
|
+
|
18
|
+
def replay_safety_disabled(&)
|
19
|
+
@replay_safety_disabled = true
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
@replay_safety_disabled = false
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(...)
|
26
|
+
if !@replay_safety_disabled && Temporalio::Workflow.in_workflow? && Temporalio::Workflow::Unsafe.replaying?
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Disable illegal call tracing for the log call
|
31
|
+
@instance.illegal_call_tracing_disabled { super }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'temporalio/scoped_logger'
|
4
|
+
|
5
|
+
module Temporalio
|
6
|
+
module Internal
|
7
|
+
module Worker
|
8
|
+
class WorkflowInstance
|
9
|
+
# Wrapper for a metric that does not log on replay.
|
10
|
+
class ReplaySafeMetric < SimpleDelegator
|
11
|
+
def record(value, additional_attributes: nil)
|
12
|
+
return if Temporalio::Workflow.in_workflow? && Temporalio::Workflow::Unsafe.replaying?
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_additional_attributes(additional_attributes)
|
18
|
+
ReplaySafeMetric.new(super)
|
19
|
+
end
|
20
|
+
|
21
|
+
class Meter < SimpleDelegator
|
22
|
+
def create_metric(
|
23
|
+
metric_type,
|
24
|
+
name,
|
25
|
+
description: nil,
|
26
|
+
unit: nil,
|
27
|
+
value_type: :integer
|
28
|
+
)
|
29
|
+
ReplaySafeMetric.new(super)
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_additional_attributes(additional_attributes)
|
33
|
+
Meter.new(super)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'temporalio'
|
4
|
+
require 'temporalio/cancellation'
|
5
|
+
require 'temporalio/error'
|
6
|
+
require 'temporalio/internal/worker/workflow_instance'
|
7
|
+
require 'temporalio/workflow'
|
8
|
+
require 'timeout'
|
9
|
+
|
10
|
+
module Temporalio
|
11
|
+
module Internal
|
12
|
+
module Worker
|
13
|
+
class WorkflowInstance
|
14
|
+
# Deterministic {::Fiber::Scheduler} implementation.
|
15
|
+
class Scheduler
|
16
|
+
def initialize(instance)
|
17
|
+
@instance = instance
|
18
|
+
@fibers = []
|
19
|
+
@ready = []
|
20
|
+
@wait_conditions = {}
|
21
|
+
@wait_condition_counter = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def context
|
25
|
+
@instance.context
|
26
|
+
end
|
27
|
+
|
28
|
+
def run_until_all_yielded
|
29
|
+
loop do
|
30
|
+
# Run all fibers until all yielded
|
31
|
+
while (fiber = @ready.shift)
|
32
|
+
fiber.resume
|
33
|
+
end
|
34
|
+
|
35
|
+
# Find the _first_ resolvable wait condition and if there, resolve it, and loop again, otherwise return.
|
36
|
+
# It is important that we both let fibers get all settled _before_ this and only allow a _single_ wait
|
37
|
+
# condition to be satisfied before looping. This allows wait condition users to trust that the line of
|
38
|
+
# code after the wait condition still has the condition satisfied.
|
39
|
+
# @type var cond_fiber: Fiber?
|
40
|
+
cond_fiber = nil
|
41
|
+
cond_result = nil
|
42
|
+
@wait_conditions.each do |seq, cond|
|
43
|
+
next unless (cond_result = cond.first.call)
|
44
|
+
|
45
|
+
cond_fiber = cond[1]
|
46
|
+
@wait_conditions.delete(seq)
|
47
|
+
break
|
48
|
+
end
|
49
|
+
return unless cond_fiber
|
50
|
+
|
51
|
+
cond_fiber.resume(cond_result)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def wait_condition(cancellation:, &block)
|
56
|
+
raise Workflow::InvalidWorkflowStateError, 'Cannot wait in this context' if @instance.context_frozen
|
57
|
+
|
58
|
+
if cancellation&.canceled?
|
59
|
+
raise Error::CanceledError,
|
60
|
+
cancellation.canceled_reason || 'Wait condition canceled before started'
|
61
|
+
end
|
62
|
+
|
63
|
+
seq = (@wait_condition_counter += 1)
|
64
|
+
@wait_conditions[seq] = [block, Fiber.current]
|
65
|
+
|
66
|
+
# Add a cancellation callback
|
67
|
+
cancel_callback_key = cancellation&.add_cancel_callback do
|
68
|
+
# Only if the condition is still present
|
69
|
+
cond = @wait_conditions.delete(seq)
|
70
|
+
if cond&.last&.alive?
|
71
|
+
cond&.last&.raise(Error::CanceledError.new(cancellation&.canceled_reason || 'Wait condition canceled'))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# This blocks until a resume is called on this fiber
|
76
|
+
result = Fiber.yield
|
77
|
+
|
78
|
+
# Remove cancellation callback (only needed on success)
|
79
|
+
cancellation&.remove_cancel_callback(cancel_callback_key) if cancel_callback_key
|
80
|
+
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def stack_trace
|
85
|
+
# Collect backtraces of known fibers, separating with a blank line. We make sure to remove any lines that
|
86
|
+
# reference Temporal paths, and we remove any empty backtraces.
|
87
|
+
dir_path = @instance.illegal_call_tracing_disabled { File.dirname(Temporalio._root_file_path) }
|
88
|
+
@fibers.map do |fiber|
|
89
|
+
fiber.backtrace.reject { |s| s.start_with?(dir_path) }.join("\n")
|
90
|
+
end.reject(&:empty?).join("\n\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
###
|
94
|
+
# Fiber::Scheduler methods
|
95
|
+
#
|
96
|
+
# Note, we do not implement many methods here such as io_read and
|
97
|
+
# such. While it might seem to make sense to implement them and
|
98
|
+
# raise, we actually want to default to the blocking behavior of them
|
99
|
+
# not being present. This is so advanced things like logging still
|
100
|
+
# work inside of workflows. So we only implement the bare minimum.
|
101
|
+
###
|
102
|
+
|
103
|
+
def block(_blocker, timeout = nil)
|
104
|
+
# TODO(cretz): Make the blocker visible in the stack trace?
|
105
|
+
|
106
|
+
# We just yield because unblock will resume this. We will just wrap in timeout if needed.
|
107
|
+
if timeout
|
108
|
+
begin
|
109
|
+
Timeout.timeout(timeout) { Fiber.yield }
|
110
|
+
true
|
111
|
+
rescue Timeout::Error
|
112
|
+
false
|
113
|
+
end
|
114
|
+
else
|
115
|
+
Fiber.yield
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def close
|
121
|
+
# Nothing to do here, lifetime of scheduler is controlled by the instance
|
122
|
+
end
|
123
|
+
|
124
|
+
def fiber(&block)
|
125
|
+
if @instance.context_frozen
|
126
|
+
raise Workflow::InvalidWorkflowStateError, 'Cannot schedule fibers in this context'
|
127
|
+
end
|
128
|
+
|
129
|
+
fiber = Fiber.new do
|
130
|
+
block.call # steep:ignore
|
131
|
+
ensure
|
132
|
+
@fibers.delete(Fiber.current)
|
133
|
+
end
|
134
|
+
@fibers << fiber
|
135
|
+
@ready << fiber
|
136
|
+
fiber
|
137
|
+
end
|
138
|
+
|
139
|
+
def io_wait(io, events, timeout)
|
140
|
+
# TODO(cretz): This in a blocking fashion?
|
141
|
+
raise NotImplementedError, 'TODO'
|
142
|
+
end
|
143
|
+
|
144
|
+
def kernel_sleep(duration = nil)
|
145
|
+
Workflow.sleep(duration)
|
146
|
+
end
|
147
|
+
|
148
|
+
def process_wait(pid, flags)
|
149
|
+
raise NotImplementedError, 'Cannot wait on other processes in workflows'
|
150
|
+
end
|
151
|
+
|
152
|
+
def timeout_after(duration, exception_class, *exception_arguments, &)
|
153
|
+
context.timeout(duration, exception_class, *exception_arguments, summary: 'Timeout timer', &)
|
154
|
+
end
|
155
|
+
|
156
|
+
def unblock(_blocker, fiber)
|
157
|
+
@ready << fiber
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|