temporalio 0.2.0 → 0.4.0

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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/Cargo.lock +980 -583
  4. data/Cargo.toml +2 -2
  5. data/Gemfile +7 -3
  6. data/README.md +769 -54
  7. data/Rakefile +10 -296
  8. data/ext/Cargo.toml +2 -0
  9. data/lib/temporalio/activity/complete_async_error.rb +1 -1
  10. data/lib/temporalio/activity/context.rb +18 -2
  11. data/lib/temporalio/activity/definition.rb +180 -65
  12. data/lib/temporalio/activity/info.rb +25 -21
  13. data/lib/temporalio/activity.rb +2 -59
  14. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  15. data/lib/temporalio/api/batch/v1/message.rb +6 -1
  16. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  17. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
  18. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
  19. data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
  20. data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
  21. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  22. data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
  23. data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
  24. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  25. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  26. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  27. data/lib/temporalio/api/command/v1/message.rb +1 -1
  28. data/lib/temporalio/api/common/v1/message.rb +8 -1
  29. data/lib/temporalio/api/deployment/v1/message.rb +38 -0
  30. data/lib/temporalio/api/enums/v1/batch_operation.rb +1 -1
  31. data/lib/temporalio/api/enums/v1/common.rb +1 -1
  32. data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
  33. data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
  34. data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
  35. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  36. data/lib/temporalio/api/enums/v1/reset.rb +1 -1
  37. data/lib/temporalio/api/enums/v1/workflow.rb +2 -1
  38. data/lib/temporalio/api/errordetails/v1/message.rb +3 -1
  39. data/lib/temporalio/api/failure/v1/message.rb +3 -1
  40. data/lib/temporalio/api/history/v1/message.rb +3 -1
  41. data/lib/temporalio/api/nexus/v1/message.rb +3 -2
  42. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  43. data/lib/temporalio/api/payload_visitor.rb +1581 -0
  44. data/lib/temporalio/api/query/v1/message.rb +2 -1
  45. data/lib/temporalio/api/schedule/v1/message.rb +2 -1
  46. data/lib/temporalio/api/taskqueue/v1/message.rb +4 -1
  47. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  48. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  49. data/lib/temporalio/api/workflow/v1/message.rb +9 -1
  50. data/lib/temporalio/api/workflowservice/v1/request_response.rb +46 -2
  51. data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
  52. data/lib/temporalio/api.rb +2 -0
  53. data/lib/temporalio/cancellation.rb +34 -14
  54. data/lib/temporalio/client/async_activity_handle.rb +12 -37
  55. data/lib/temporalio/client/connection/cloud_service.rb +309 -231
  56. data/lib/temporalio/client/connection/operator_service.rb +36 -84
  57. data/lib/temporalio/client/connection/service.rb +6 -5
  58. data/lib/temporalio/client/connection/test_service.rb +111 -0
  59. data/lib/temporalio/client/connection/workflow_service.rb +474 -441
  60. data/lib/temporalio/client/connection.rb +90 -44
  61. data/lib/temporalio/client/interceptor.rb +199 -60
  62. data/lib/temporalio/client/schedule.rb +991 -0
  63. data/lib/temporalio/client/schedule_handle.rb +126 -0
  64. data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
  65. data/lib/temporalio/client/workflow_execution.rb +26 -10
  66. data/lib/temporalio/client/workflow_handle.rb +41 -98
  67. data/lib/temporalio/client/workflow_update_handle.rb +3 -5
  68. data/lib/temporalio/client.rb +247 -44
  69. data/lib/temporalio/common_enums.rb +17 -0
  70. data/lib/temporalio/contrib/open_telemetry.rb +470 -0
  71. data/lib/temporalio/converters/data_converter.rb +4 -7
  72. data/lib/temporalio/converters/failure_converter.rb +5 -3
  73. data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
  74. data/lib/temporalio/converters/payload_converter.rb +6 -8
  75. data/lib/temporalio/converters/raw_value.rb +20 -0
  76. data/lib/temporalio/error/failure.rb +1 -1
  77. data/lib/temporalio/error.rb +11 -2
  78. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +1 -1
  79. data/lib/temporalio/internal/bridge/api/common/common.rb +2 -1
  80. data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
  81. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  82. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
  83. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
  84. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +2 -1
  85. data/lib/temporalio/internal/bridge/client.rb +11 -6
  86. data/lib/temporalio/internal/bridge/runtime.rb +3 -0
  87. data/lib/temporalio/internal/bridge/testing.rb +23 -0
  88. data/lib/temporalio/internal/bridge/worker.rb +2 -0
  89. data/lib/temporalio/internal/bridge.rb +1 -1
  90. data/lib/temporalio/internal/client/implementation.rb +468 -71
  91. data/lib/temporalio/internal/metric.rb +122 -0
  92. data/lib/temporalio/internal/proto_utils.rb +118 -7
  93. data/lib/temporalio/internal/worker/activity_worker.rb +69 -29
  94. data/lib/temporalio/internal/worker/multi_runner.rb +53 -9
  95. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  96. data/lib/temporalio/internal/worker/workflow_instance/context.rb +383 -0
  97. data/lib/temporalio/internal/worker/workflow_instance/details.rb +46 -0
  98. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  99. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  100. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  101. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  102. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  103. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  104. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +400 -0
  105. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  106. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  107. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +183 -0
  108. data/lib/temporalio/internal/worker/workflow_instance.rb +774 -0
  109. data/lib/temporalio/internal/worker/workflow_worker.rb +239 -0
  110. data/lib/temporalio/metric.rb +109 -0
  111. data/lib/temporalio/retry_policy.rb +37 -14
  112. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  113. data/lib/temporalio/runtime.rb +160 -79
  114. data/lib/temporalio/search_attributes.rb +93 -37
  115. data/lib/temporalio/testing/activity_environment.rb +44 -16
  116. data/lib/temporalio/testing/workflow_environment.rb +276 -7
  117. data/lib/temporalio/version.rb +1 -1
  118. data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
  119. data/lib/temporalio/worker/activity_executor.rb +3 -3
  120. data/lib/temporalio/worker/interceptor.rb +343 -66
  121. data/lib/temporalio/worker/thread_pool.rb +237 -0
  122. data/lib/temporalio/worker/tuner.rb +38 -0
  123. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +235 -0
  124. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  125. data/lib/temporalio/worker/workflow_replayer.rb +350 -0
  126. data/lib/temporalio/worker.rb +235 -58
  127. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  128. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  129. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  130. data/lib/temporalio/workflow/definition.rb +598 -0
  131. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  132. data/lib/temporalio/workflow/future.rb +151 -0
  133. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  134. data/lib/temporalio/workflow/info.rb +104 -0
  135. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  136. data/lib/temporalio/workflow/update_info.rb +20 -0
  137. data/lib/temporalio/workflow.rb +575 -0
  138. data/lib/temporalio/workflow_history.rb +26 -1
  139. data/lib/temporalio.rb +4 -0
  140. data/temporalio.gemspec +4 -3
  141. metadata +73 -10
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/internal/worker/workflow_instance'
4
+ require 'temporalio/worker/interceptor'
5
+ require 'temporalio/workflow'
6
+
7
+ module Temporalio
8
+ module Internal
9
+ module Worker
10
+ class WorkflowInstance
11
+ # Root implementation of the inbound interceptor.
12
+ class InboundImplementation < Temporalio::Worker::Interceptor::Workflow::Inbound
13
+ def initialize(instance)
14
+ super(nil) # steep:ignore
15
+ @instance = instance
16
+ end
17
+
18
+ def init(outbound)
19
+ @instance.context._outbound = outbound
20
+ end
21
+
22
+ def execute(input)
23
+ @instance.instance.execute(*input.args)
24
+ end
25
+
26
+ def handle_signal(input)
27
+ invoke_handler(input.signal, input)
28
+ end
29
+
30
+ def handle_query(input)
31
+ invoke_handler(input.query, input)
32
+ end
33
+
34
+ def validate_update(input)
35
+ invoke_handler(input.update, input, to_invoke: input.definition.validator_to_invoke)
36
+ end
37
+
38
+ def handle_update(input)
39
+ invoke_handler(input.update, input)
40
+ end
41
+
42
+ private
43
+
44
+ def invoke_handler(name, input, to_invoke: input.definition.to_invoke)
45
+ args = input.args
46
+ # Add name as first param if dynamic
47
+ args = [name] + args if input.definition.name.nil?
48
+ # Assume symbol or proc
49
+ case to_invoke
50
+ when Symbol
51
+ @instance.instance.send(to_invoke, *args)
52
+ when Proc
53
+ to_invoke.call(*args)
54
+ else
55
+ raise "Unrecognized invocation type #{to_invoke.class}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,400 @@
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
+ execute_activity_with_local_backoffs(local: false, cancellation: input.cancellation) do
60
+ seq = (@activity_counter += 1)
61
+ @instance.add_command(
62
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
63
+ schedule_activity: Bridge::Api::WorkflowCommands::ScheduleActivity.new(
64
+ seq:,
65
+ activity_id: input.activity_id || seq.to_s,
66
+ activity_type: input.activity,
67
+ task_queue: input.task_queue,
68
+ headers: ProtoUtils.headers_to_proto_hash(input.headers, @instance.payload_converter),
69
+ arguments: ProtoUtils.convert_to_payload_array(@instance.payload_converter, input.args),
70
+ schedule_to_close_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_close_timeout),
71
+ schedule_to_start_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_start_timeout),
72
+ start_to_close_timeout: ProtoUtils.seconds_to_duration(input.start_to_close_timeout),
73
+ heartbeat_timeout: ProtoUtils.seconds_to_duration(input.heartbeat_timeout),
74
+ retry_policy: input.retry_policy&._to_proto,
75
+ cancellation_type: input.cancellation_type,
76
+ do_not_eagerly_execute: input.disable_eager_execution
77
+ ),
78
+ user_metadata: ProtoUtils.to_user_metadata(input.summary, nil, @instance.payload_converter)
79
+ )
80
+ )
81
+ seq
82
+ end
83
+ end
84
+
85
+ def execute_local_activity(input)
86
+ if input.schedule_to_close_timeout.nil? && input.start_to_close_timeout.nil?
87
+ raise ArgumentError, 'Activity must have schedule_to_close_timeout or start_to_close_timeout'
88
+ end
89
+
90
+ execute_activity_with_local_backoffs(local: true, cancellation: input.cancellation) do |do_backoff|
91
+ seq = (@activity_counter += 1)
92
+ @instance.add_command(
93
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
94
+ schedule_local_activity: Bridge::Api::WorkflowCommands::ScheduleLocalActivity.new(
95
+ seq:,
96
+ activity_id: input.activity_id || seq.to_s,
97
+ activity_type: input.activity,
98
+ headers: ProtoUtils.headers_to_proto_hash(input.headers, @instance.payload_converter),
99
+ arguments: ProtoUtils.convert_to_payload_array(@instance.payload_converter, input.args),
100
+ schedule_to_close_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_close_timeout),
101
+ schedule_to_start_timeout: ProtoUtils.seconds_to_duration(input.schedule_to_start_timeout),
102
+ start_to_close_timeout: ProtoUtils.seconds_to_duration(input.start_to_close_timeout),
103
+ retry_policy: input.retry_policy&._to_proto,
104
+ cancellation_type: input.cancellation_type,
105
+ local_retry_threshold: ProtoUtils.seconds_to_duration(input.local_retry_threshold),
106
+ attempt: do_backoff&.attempt || 0,
107
+ original_schedule_time: do_backoff&.original_schedule_time
108
+ )
109
+ )
110
+ )
111
+ seq
112
+ end
113
+ end
114
+
115
+ def execute_activity_with_local_backoffs(local:, cancellation:, &)
116
+ # We do not even want to schedule if the cancellation is already cancelled. We choose to use canceled
117
+ # failure instead of wrapping in activity failure which is similar to what other SDKs do, with the accepted
118
+ # tradeoff that it makes rescue more difficult (hence the presence of Error.canceled? helper).
119
+ raise Error::CanceledError, 'Activity canceled before scheduled' if cancellation.canceled?
120
+
121
+ # This has to be done in a loop for local activity backoff
122
+ last_local_backoff = nil
123
+ loop do
124
+ result = execute_activity_once(local:, cancellation:, last_local_backoff:, &)
125
+ return result unless result.is_a?(Bridge::Api::ActivityResult::DoBackoff)
126
+
127
+ # @type var result: untyped
128
+ last_local_backoff = result
129
+ # Have to sleep the amount of the backoff, which can be canceled with the same cancellation
130
+ # TODO(cretz): What should this cancellation raise?
131
+ Workflow.sleep(ProtoUtils.duration_to_seconds(result.backoff_duration), cancellation:)
132
+ end
133
+ end
134
+
135
+ # If this doesn't raise, it returns success | DoBackoff
136
+ def execute_activity_once(local:, cancellation:, last_local_backoff:, &)
137
+ # Add to pending activities (removed by the resolver)
138
+ seq = yield last_local_backoff
139
+ @instance.pending_activities[seq] = Fiber.current
140
+
141
+ # Add cancellation hook
142
+ cancel_callback_key = cancellation.add_cancel_callback do
143
+ # Only if the activity is present still
144
+ if @instance.pending_activities.include?(seq)
145
+ if local
146
+ @instance.add_command(
147
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
148
+ request_cancel_local_activity: Bridge::Api::WorkflowCommands::RequestCancelLocalActivity.new(seq:)
149
+ )
150
+ )
151
+ else
152
+ @instance.add_command(
153
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
154
+ request_cancel_activity: Bridge::Api::WorkflowCommands::RequestCancelActivity.new(seq:)
155
+ )
156
+ )
157
+ end
158
+ end
159
+ end
160
+
161
+ # Wait
162
+ resolution = Fiber.yield
163
+
164
+ # Remove cancellation callback
165
+ cancellation.remove_cancel_callback(cancel_callback_key)
166
+
167
+ case resolution.status
168
+ when :completed
169
+ @instance.payload_converter.from_payload(resolution.completed.result)
170
+ when :failed
171
+ raise @instance.failure_converter.from_failure(resolution.failed.failure, @instance.payload_converter)
172
+ when :cancelled
173
+ raise @instance.failure_converter.from_failure(resolution.cancelled.failure, @instance.payload_converter)
174
+ when :backoff
175
+ resolution.backoff
176
+ else
177
+ raise "Unrecognized resolution status: #{resolution.status}"
178
+ end
179
+ end
180
+
181
+ def initialize_continue_as_new_error(input)
182
+ # Do nothing
183
+ end
184
+
185
+ def signal_child_workflow(input)
186
+ _signal_external_workflow(
187
+ id: input.id,
188
+ run_id: nil,
189
+ child: true,
190
+ signal: input.signal,
191
+ args: input.args,
192
+ cancellation: input.cancellation,
193
+ headers: input.headers
194
+ )
195
+ end
196
+
197
+ def signal_external_workflow(input)
198
+ _signal_external_workflow(
199
+ id: input.id,
200
+ run_id: input.run_id,
201
+ child: false,
202
+ signal: input.signal,
203
+ args: input.args,
204
+ cancellation: input.cancellation,
205
+ headers: input.headers
206
+ )
207
+ end
208
+
209
+ def _signal_external_workflow(id:, run_id:, child:, signal:, args:, cancellation:, headers:)
210
+ raise Error::CanceledError, 'Signal canceled before scheduled' if cancellation.canceled?
211
+
212
+ # Add command
213
+ seq = (@external_signal_counter += 1)
214
+ cmd = Bridge::Api::WorkflowCommands::SignalExternalWorkflowExecution.new(
215
+ seq:,
216
+ signal_name: signal,
217
+ args: ProtoUtils.convert_to_payload_array(@instance.payload_converter, args),
218
+ headers: ProtoUtils.headers_to_proto_hash(headers, @instance.payload_converter)
219
+ )
220
+ if child
221
+ cmd.child_workflow_id = id
222
+ else
223
+ cmd.workflow_execution = Bridge::Api::Common::NamespacedWorkflowExecution.new(
224
+ namespace: @instance.info.namespace,
225
+ workflow_id: id,
226
+ run_id:
227
+ )
228
+ end
229
+ @instance.add_command(
230
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(signal_external_workflow_execution: cmd)
231
+ )
232
+ @instance.pending_external_signals[seq] = Fiber.current
233
+
234
+ # Add a cancellation callback
235
+ cancel_callback_key = cancellation.add_cancel_callback do
236
+ # Add the command but do not raise, we will let resolution do that
237
+ @instance.add_command(
238
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
239
+ cancel_signal_workflow: Bridge::Api::WorkflowCommands::CancelSignalWorkflow.new(seq:)
240
+ )
241
+ )
242
+ end
243
+
244
+ # Wait
245
+ resolution = Fiber.yield
246
+
247
+ # Remove cancellation callback
248
+ cancellation.remove_cancel_callback(cancel_callback_key)
249
+
250
+ # Raise if resolution has failure
251
+ return unless resolution.failure
252
+
253
+ raise @instance.failure_converter.from_failure(resolution.failure, @instance.payload_converter)
254
+ end
255
+
256
+ def sleep(input)
257
+ # If already cancelled, raise as such
258
+ if input.cancellation.canceled?
259
+ raise Error::CanceledError,
260
+ input.cancellation.canceled_reason || 'Timer canceled before started'
261
+ end
262
+
263
+ # Disallow negative durations
264
+ raise ArgumentError, 'Sleep duration cannot be less than 0' if input.duration&.negative?
265
+
266
+ # If the duration is infinite, just wait for cancellation
267
+ if input.duration.nil?
268
+ input.cancellation.wait
269
+ raise Error::CanceledError, input.cancellation.canceled_reason || 'Timer canceled'
270
+ end
271
+
272
+ # If duration is zero, we make it one millisecond. It was decided a 0 duration still makes a timer to ensure
273
+ # determinism if a timer's duration is altered from non-zero to zero or vice versa.
274
+ duration = input.duration
275
+ duration = 0.001 if duration.zero?
276
+
277
+ # Add command
278
+ seq = (@timer_counter += 1)
279
+ @instance.add_command(
280
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
281
+ start_timer: Bridge::Api::WorkflowCommands::StartTimer.new(
282
+ seq:,
283
+ start_to_fire_timeout: ProtoUtils.seconds_to_duration(duration)
284
+ ),
285
+ user_metadata: ProtoUtils.to_user_metadata(input.summary, nil, @instance.payload_converter)
286
+ )
287
+ )
288
+ @instance.pending_timers[seq] = Fiber.current
289
+
290
+ # Add a cancellation callback
291
+ cancel_callback_key = input.cancellation.add_cancel_callback do
292
+ # Only if the timer is still present
293
+ fiber = @instance.pending_timers.delete(seq)
294
+ if fiber
295
+ # Add the command for cancel then raise
296
+ @instance.add_command(
297
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
298
+ cancel_timer: Bridge::Api::WorkflowCommands::CancelTimer.new(seq:)
299
+ )
300
+ )
301
+ if fiber.alive?
302
+ fiber.raise(Error::CanceledError.new(input.cancellation.canceled_reason || 'Timer canceled'))
303
+ end
304
+ end
305
+ end
306
+
307
+ # Wait
308
+ Fiber.yield
309
+
310
+ # Remove cancellation callback (only needed on success)
311
+ input.cancellation.remove_cancel_callback(cancel_callback_key)
312
+ end
313
+
314
+ def start_child_workflow(input)
315
+ raise Error::CanceledError, 'Child canceled before scheduled' if input.cancellation.canceled?
316
+
317
+ # Add the command
318
+ seq = (@child_counter += 1)
319
+ @instance.add_command(
320
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
321
+ start_child_workflow_execution: Bridge::Api::WorkflowCommands::StartChildWorkflowExecution.new(
322
+ seq:,
323
+ namespace: @instance.info.namespace,
324
+ workflow_id: input.id,
325
+ workflow_type: input.workflow,
326
+ task_queue: input.task_queue,
327
+ input: ProtoUtils.convert_to_payload_array(@instance.payload_converter, input.args),
328
+ workflow_execution_timeout: ProtoUtils.seconds_to_duration(input.execution_timeout),
329
+ workflow_run_timeout: ProtoUtils.seconds_to_duration(input.run_timeout),
330
+ workflow_task_timeout: ProtoUtils.seconds_to_duration(input.task_timeout),
331
+ parent_close_policy: input.parent_close_policy,
332
+ workflow_id_reuse_policy: input.id_reuse_policy,
333
+ retry_policy: input.retry_policy&._to_proto,
334
+ cron_schedule: input.cron_schedule,
335
+ headers: ProtoUtils.headers_to_proto_hash(input.headers, @instance.payload_converter),
336
+ memo: ProtoUtils.memo_to_proto_hash(input.memo, @instance.payload_converter),
337
+ search_attributes: input.search_attributes&._to_proto_hash,
338
+ cancellation_type: input.cancellation_type
339
+ ),
340
+ user_metadata: ProtoUtils.to_user_metadata(
341
+ input.static_summary, input.static_details, @instance.payload_converter
342
+ )
343
+ )
344
+ )
345
+
346
+ # Set as pending start and register cancel callback
347
+ @instance.pending_child_workflow_starts[seq] = Fiber.current
348
+ cancel_callback_key = input.cancellation.add_cancel_callback do
349
+ # Send cancel if in start or pending
350
+ if @instance.pending_child_workflow_starts.include?(seq) ||
351
+ @instance.pending_child_workflows.include?(seq)
352
+ @instance.add_command(
353
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
354
+ cancel_child_workflow_execution: Bridge::Api::WorkflowCommands::CancelChildWorkflowExecution.new(
355
+ child_workflow_seq: seq
356
+ )
357
+ )
358
+ )
359
+ end
360
+ end
361
+
362
+ # Wait for start
363
+ resolution = Fiber.yield
364
+
365
+ case resolution.status
366
+ when :succeeded
367
+ # Create handle, passing along the cancel callback key, and set it as pending
368
+ handle = ChildWorkflowHandle.new(
369
+ id: input.id,
370
+ first_execution_run_id: resolution.succeeded.run_id,
371
+ instance: @instance,
372
+ cancellation: input.cancellation,
373
+ cancel_callback_key:
374
+ )
375
+ @instance.pending_child_workflows[seq] = handle
376
+ handle
377
+ when :failed
378
+ # Remove cancel callback and handle failure
379
+ input.cancellation.remove_cancel_callback(cancel_callback_key)
380
+ if resolution.failed.cause == :START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_EXISTS
381
+ raise Error::WorkflowAlreadyStartedError.new(
382
+ workflow_id: resolution.failed.workflow_id,
383
+ workflow_type: resolution.failed.workflow_type,
384
+ run_id: nil
385
+ )
386
+ end
387
+ raise "Unknown child start fail cause: #{resolution.failed.cause}"
388
+ when :cancelled
389
+ # Remove cancel callback and handle cancel
390
+ input.cancellation.remove_cancel_callback(cancel_callback_key)
391
+ raise @instance.failure_converter.from_failure(resolution.cancelled.failure, @instance.payload_converter)
392
+ else
393
+ raise "Unknown resolution status: #{resolution.status}"
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end
399
+ end
400
+ 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