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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/Gemfile +3 -3
  4. data/Rakefile +10 -296
  5. data/lib/temporalio/activity/complete_async_error.rb +1 -1
  6. data/lib/temporalio/activity/context.rb +5 -2
  7. data/lib/temporalio/activity/definition.rb +163 -65
  8. data/lib/temporalio/activity/info.rb +22 -21
  9. data/lib/temporalio/activity.rb +2 -59
  10. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  11. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  12. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
  13. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
  14. data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
  15. data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
  16. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  17. data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
  18. data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
  19. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  20. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  21. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  22. data/lib/temporalio/api/common/v1/message.rb +7 -1
  23. data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
  24. data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
  25. data/lib/temporalio/api/enums/v1/reset.rb +1 -1
  26. data/lib/temporalio/api/history/v1/message.rb +1 -1
  27. data/lib/temporalio/api/nexus/v1/message.rb +2 -2
  28. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  29. data/lib/temporalio/api/payload_visitor.rb +1513 -0
  30. data/lib/temporalio/api/schedule/v1/message.rb +2 -1
  31. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  32. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  33. data/lib/temporalio/api/workflow/v1/message.rb +1 -1
  34. data/lib/temporalio/api/workflowservice/v1/request_response.rb +17 -2
  35. data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
  36. data/lib/temporalio/api.rb +1 -0
  37. data/lib/temporalio/cancellation.rb +34 -14
  38. data/lib/temporalio/client/async_activity_handle.rb +12 -37
  39. data/lib/temporalio/client/connection/cloud_service.rb +309 -231
  40. data/lib/temporalio/client/connection/operator_service.rb +36 -84
  41. data/lib/temporalio/client/connection/service.rb +6 -5
  42. data/lib/temporalio/client/connection/test_service.rb +111 -0
  43. data/lib/temporalio/client/connection/workflow_service.rb +264 -441
  44. data/lib/temporalio/client/connection.rb +90 -44
  45. data/lib/temporalio/client/interceptor.rb +160 -60
  46. data/lib/temporalio/client/schedule.rb +967 -0
  47. data/lib/temporalio/client/schedule_handle.rb +126 -0
  48. data/lib/temporalio/client/workflow_execution.rb +7 -10
  49. data/lib/temporalio/client/workflow_handle.rb +38 -95
  50. data/lib/temporalio/client/workflow_update_handle.rb +3 -5
  51. data/lib/temporalio/client.rb +122 -42
  52. data/lib/temporalio/common_enums.rb +17 -0
  53. data/lib/temporalio/converters/data_converter.rb +4 -7
  54. data/lib/temporalio/converters/failure_converter.rb +5 -3
  55. data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
  56. data/lib/temporalio/converters/payload_converter.rb +6 -8
  57. data/lib/temporalio/converters/raw_value.rb +20 -0
  58. data/lib/temporalio/error/failure.rb +1 -1
  59. data/lib/temporalio/error.rb +10 -2
  60. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.bundle +0 -0
  61. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.bundle +0 -0
  62. data/lib/temporalio/internal/bridge/{3.1 → 3.4}/temporalio_bridge.bundle +0 -0
  63. data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
  64. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  65. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
  66. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
  67. data/lib/temporalio/internal/bridge/client.rb +11 -6
  68. data/lib/temporalio/internal/bridge/testing.rb +20 -0
  69. data/lib/temporalio/internal/bridge/worker.rb +2 -0
  70. data/lib/temporalio/internal/bridge.rb +1 -1
  71. data/lib/temporalio/internal/client/implementation.rb +245 -70
  72. data/lib/temporalio/internal/metric.rb +122 -0
  73. data/lib/temporalio/internal/proto_utils.rb +86 -7
  74. data/lib/temporalio/internal/worker/activity_worker.rb +52 -24
  75. data/lib/temporalio/internal/worker/multi_runner.rb +51 -7
  76. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  77. data/lib/temporalio/internal/worker/workflow_instance/context.rb +329 -0
  78. data/lib/temporalio/internal/worker/workflow_instance/details.rb +44 -0
  79. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  80. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  81. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  82. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  83. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  84. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  85. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +415 -0
  86. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  87. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  88. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +163 -0
  89. data/lib/temporalio/internal/worker/workflow_instance.rb +730 -0
  90. data/lib/temporalio/internal/worker/workflow_worker.rb +196 -0
  91. data/lib/temporalio/metric.rb +109 -0
  92. data/lib/temporalio/retry_policy.rb +37 -14
  93. data/lib/temporalio/runtime.rb +118 -75
  94. data/lib/temporalio/search_attributes.rb +80 -37
  95. data/lib/temporalio/testing/activity_environment.rb +2 -2
  96. data/lib/temporalio/testing/workflow_environment.rb +251 -5
  97. data/lib/temporalio/version.rb +1 -1
  98. data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
  99. data/lib/temporalio/worker/activity_executor.rb +3 -3
  100. data/lib/temporalio/worker/interceptor.rb +340 -66
  101. data/lib/temporalio/worker/thread_pool.rb +237 -0
  102. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +230 -0
  103. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  104. data/lib/temporalio/worker.rb +201 -30
  105. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  106. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  107. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  108. data/lib/temporalio/workflow/definition.rb +566 -0
  109. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  110. data/lib/temporalio/workflow/future.rb +151 -0
  111. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  112. data/lib/temporalio/workflow/info.rb +82 -0
  113. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  114. data/lib/temporalio/workflow/update_info.rb +20 -0
  115. data/lib/temporalio/workflow.rb +523 -0
  116. data/lib/temporalio.rb +4 -0
  117. data/temporalio.gemspec +2 -2
  118. 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