temporalio 0.4.0-aarch64-linux-musl

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 (183) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +2 -0
  3. data/Gemfile +27 -0
  4. data/Rakefile +101 -0
  5. data/lib/temporalio/activity/complete_async_error.rb +11 -0
  6. data/lib/temporalio/activity/context.rb +123 -0
  7. data/lib/temporalio/activity/definition.rb +192 -0
  8. data/lib/temporalio/activity/info.rb +67 -0
  9. data/lib/temporalio/activity.rb +12 -0
  10. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  11. data/lib/temporalio/api/batch/v1/message.rb +36 -0
  12. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  13. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +126 -0
  14. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
  15. data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
  16. data/lib/temporalio/api/cloud/identity/v1/message.rb +41 -0
  17. data/lib/temporalio/api/cloud/namespace/v1/message.rb +42 -0
  18. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  19. data/lib/temporalio/api/cloud/operation/v1/message.rb +28 -0
  20. data/lib/temporalio/api/cloud/region/v1/message.rb +24 -0
  21. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  22. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  23. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  24. data/lib/temporalio/api/command/v1/message.rb +46 -0
  25. data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
  26. data/lib/temporalio/api/common/v1/message.rb +48 -0
  27. data/lib/temporalio/api/deployment/v1/message.rb +38 -0
  28. data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
  29. data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
  30. data/lib/temporalio/api/enums/v1/common.rb +26 -0
  31. data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
  32. data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
  33. data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
  34. data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
  35. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  36. data/lib/temporalio/api/enums/v1/query.rb +22 -0
  37. data/lib/temporalio/api/enums/v1/reset.rb +23 -0
  38. data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
  39. data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
  40. data/lib/temporalio/api/enums/v1/update.rb +22 -0
  41. data/lib/temporalio/api/enums/v1/workflow.rb +31 -0
  42. data/lib/temporalio/api/errordetails/v1/message.rb +44 -0
  43. data/lib/temporalio/api/export/v1/message.rb +24 -0
  44. data/lib/temporalio/api/failure/v1/message.rb +37 -0
  45. data/lib/temporalio/api/filter/v1/message.rb +27 -0
  46. data/lib/temporalio/api/history/v1/message.rb +92 -0
  47. data/lib/temporalio/api/namespace/v1/message.rb +31 -0
  48. data/lib/temporalio/api/nexus/v1/message.rb +41 -0
  49. data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
  50. data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
  51. data/lib/temporalio/api/operatorservice.rb +3 -0
  52. data/lib/temporalio/api/payload_visitor.rb +1581 -0
  53. data/lib/temporalio/api/protocol/v1/message.rb +23 -0
  54. data/lib/temporalio/api/query/v1/message.rb +28 -0
  55. data/lib/temporalio/api/replication/v1/message.rb +26 -0
  56. data/lib/temporalio/api/schedule/v1/message.rb +43 -0
  57. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +25 -0
  58. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +21 -0
  59. data/lib/temporalio/api/sdk/v1/user_metadata.rb +23 -0
  60. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +23 -0
  61. data/lib/temporalio/api/taskqueue/v1/message.rb +48 -0
  62. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  63. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  64. data/lib/temporalio/api/update/v1/message.rb +33 -0
  65. data/lib/temporalio/api/version/v1/message.rb +26 -0
  66. data/lib/temporalio/api/workflow/v1/message.rb +51 -0
  67. data/lib/temporalio/api/workflowservice/v1/request_response.rb +233 -0
  68. data/lib/temporalio/api/workflowservice/v1/service.rb +23 -0
  69. data/lib/temporalio/api/workflowservice.rb +3 -0
  70. data/lib/temporalio/api.rb +15 -0
  71. data/lib/temporalio/cancellation.rb +170 -0
  72. data/lib/temporalio/client/activity_id_reference.rb +32 -0
  73. data/lib/temporalio/client/async_activity_handle.rb +85 -0
  74. data/lib/temporalio/client/connection/cloud_service.rb +726 -0
  75. data/lib/temporalio/client/connection/operator_service.rb +201 -0
  76. data/lib/temporalio/client/connection/service.rb +42 -0
  77. data/lib/temporalio/client/connection/test_service.rb +111 -0
  78. data/lib/temporalio/client/connection/workflow_service.rb +1251 -0
  79. data/lib/temporalio/client/connection.rb +316 -0
  80. data/lib/temporalio/client/interceptor.rb +455 -0
  81. data/lib/temporalio/client/schedule.rb +991 -0
  82. data/lib/temporalio/client/schedule_handle.rb +126 -0
  83. data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
  84. data/lib/temporalio/client/workflow_execution.rb +119 -0
  85. data/lib/temporalio/client/workflow_execution_count.rb +36 -0
  86. data/lib/temporalio/client/workflow_execution_status.rb +18 -0
  87. data/lib/temporalio/client/workflow_handle.rb +389 -0
  88. data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
  89. data/lib/temporalio/client/workflow_update_handle.rb +65 -0
  90. data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
  91. data/lib/temporalio/client.rb +607 -0
  92. data/lib/temporalio/common_enums.rb +41 -0
  93. data/lib/temporalio/contrib/open_telemetry.rb +470 -0
  94. data/lib/temporalio/converters/data_converter.rb +99 -0
  95. data/lib/temporalio/converters/failure_converter.rb +202 -0
  96. data/lib/temporalio/converters/payload_codec.rb +26 -0
  97. data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
  98. data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
  99. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
  100. data/lib/temporalio/converters/payload_converter/composite.rb +66 -0
  101. data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
  102. data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
  103. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
  104. data/lib/temporalio/converters/payload_converter.rb +71 -0
  105. data/lib/temporalio/converters/raw_value.rb +20 -0
  106. data/lib/temporalio/converters.rb +9 -0
  107. data/lib/temporalio/error/failure.rb +219 -0
  108. data/lib/temporalio/error.rb +156 -0
  109. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  110. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  111. data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +0 -0
  112. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
  113. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +31 -0
  114. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
  115. data/lib/temporalio/internal/bridge/api/common/common.rb +27 -0
  116. data/lib/temporalio/internal/bridge/api/core_interface.rb +40 -0
  117. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
  118. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  119. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +56 -0
  120. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +57 -0
  121. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +31 -0
  122. data/lib/temporalio/internal/bridge/api.rb +3 -0
  123. data/lib/temporalio/internal/bridge/client.rb +95 -0
  124. data/lib/temporalio/internal/bridge/runtime.rb +56 -0
  125. data/lib/temporalio/internal/bridge/testing.rb +69 -0
  126. data/lib/temporalio/internal/bridge/worker.rb +85 -0
  127. data/lib/temporalio/internal/bridge.rb +36 -0
  128. data/lib/temporalio/internal/client/implementation.rb +922 -0
  129. data/lib/temporalio/internal/metric.rb +122 -0
  130. data/lib/temporalio/internal/proto_utils.rb +165 -0
  131. data/lib/temporalio/internal/worker/activity_worker.rb +385 -0
  132. data/lib/temporalio/internal/worker/multi_runner.rb +213 -0
  133. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  134. data/lib/temporalio/internal/worker/workflow_instance/context.rb +383 -0
  135. data/lib/temporalio/internal/worker/workflow_instance/details.rb +46 -0
  136. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  137. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  138. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  139. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  140. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  141. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  142. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +400 -0
  143. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  144. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  145. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +183 -0
  146. data/lib/temporalio/internal/worker/workflow_instance.rb +774 -0
  147. data/lib/temporalio/internal/worker/workflow_worker.rb +239 -0
  148. data/lib/temporalio/internal.rb +7 -0
  149. data/lib/temporalio/metric.rb +109 -0
  150. data/lib/temporalio/retry_policy.rb +74 -0
  151. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  152. data/lib/temporalio/runtime.rb +352 -0
  153. data/lib/temporalio/scoped_logger.rb +96 -0
  154. data/lib/temporalio/search_attributes.rb +356 -0
  155. data/lib/temporalio/testing/activity_environment.rb +160 -0
  156. data/lib/temporalio/testing/workflow_environment.rb +406 -0
  157. data/lib/temporalio/testing.rb +10 -0
  158. data/lib/temporalio/version.rb +5 -0
  159. data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
  160. data/lib/temporalio/worker/activity_executor/thread_pool.rb +46 -0
  161. data/lib/temporalio/worker/activity_executor.rb +55 -0
  162. data/lib/temporalio/worker/interceptor.rb +365 -0
  163. data/lib/temporalio/worker/thread_pool.rb +237 -0
  164. data/lib/temporalio/worker/tuner.rb +189 -0
  165. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +235 -0
  166. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  167. data/lib/temporalio/worker/workflow_replayer.rb +350 -0
  168. data/lib/temporalio/worker.rb +603 -0
  169. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  170. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  171. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  172. data/lib/temporalio/workflow/definition.rb +598 -0
  173. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  174. data/lib/temporalio/workflow/future.rb +151 -0
  175. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  176. data/lib/temporalio/workflow/info.rb +104 -0
  177. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  178. data/lib/temporalio/workflow/update_info.rb +20 -0
  179. data/lib/temporalio/workflow.rb +575 -0
  180. data/lib/temporalio/workflow_history.rb +47 -0
  181. data/lib/temporalio.rb +11 -0
  182. data/temporalio.gemspec +29 -0
  183. metadata +258 -0
@@ -0,0 +1,774 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'temporalio'
5
+ require 'temporalio/activity/definition'
6
+ require 'temporalio/api'
7
+ require 'temporalio/converters/payload_converter'
8
+ require 'temporalio/converters/raw_value'
9
+ require 'temporalio/error'
10
+ require 'temporalio/internal/bridge/api'
11
+ require 'temporalio/internal/proto_utils'
12
+ require 'temporalio/internal/worker/workflow_instance/child_workflow_handle'
13
+ require 'temporalio/internal/worker/workflow_instance/context'
14
+ require 'temporalio/internal/worker/workflow_instance/details'
15
+ require 'temporalio/internal/worker/workflow_instance/externally_immutable_hash'
16
+ require 'temporalio/internal/worker/workflow_instance/handler_execution'
17
+ require 'temporalio/internal/worker/workflow_instance/handler_hash'
18
+ require 'temporalio/internal/worker/workflow_instance/illegal_call_tracer'
19
+ require 'temporalio/internal/worker/workflow_instance/inbound_implementation'
20
+ require 'temporalio/internal/worker/workflow_instance/outbound_implementation'
21
+ require 'temporalio/internal/worker/workflow_instance/replay_safe_logger'
22
+ require 'temporalio/internal/worker/workflow_instance/replay_safe_metric'
23
+ require 'temporalio/internal/worker/workflow_instance/scheduler'
24
+ require 'temporalio/retry_policy'
25
+ require 'temporalio/scoped_logger'
26
+ require 'temporalio/worker/interceptor'
27
+ require 'temporalio/workflow/info'
28
+ require 'temporalio/workflow/update_info'
29
+ require 'timeout'
30
+
31
+ module Temporalio
32
+ module Internal
33
+ module Worker
34
+ # Instance of a user workflow. This is the instance with all state needed to run the workflow and is expected to
35
+ # be cached by the worker for sticky execution.
36
+ class WorkflowInstance
37
+ def self.new_completion_with_failure(run_id:, error:, failure_converter:, payload_converter:)
38
+ Bridge::Api::WorkflowCompletion::WorkflowActivationCompletion.new(
39
+ run_id: run_id,
40
+ failed: Bridge::Api::WorkflowCompletion::Failure.new(
41
+ failure: begin
42
+ failure_converter.to_failure(error, payload_converter)
43
+ rescue Exception => e # rubocop:disable Lint/RescueException
44
+ Api::Failure::V1::Failure.new(
45
+ message: "Failed converting error to failure: #{e.message}, " \
46
+ "original error message: #{error.message}",
47
+ application_failure_info: Api::Failure::V1::ApplicationFailureInfo.new
48
+ )
49
+ end
50
+ )
51
+ )
52
+ end
53
+
54
+ attr_reader :context, :logger, :info, :scheduler, :disable_eager_activity_execution, :pending_activities,
55
+ :pending_timers, :pending_child_workflow_starts, :pending_child_workflows,
56
+ :pending_external_signals, :pending_external_cancels, :in_progress_handlers, :payload_converter,
57
+ :failure_converter, :cancellation, :continue_as_new_suggested, :current_history_length,
58
+ :current_history_size, :replaying, :random, :signal_handlers, :query_handlers, :update_handlers,
59
+ :context_frozen
60
+ attr_accessor :io_enabled, :current_details
61
+
62
+ def initialize(details)
63
+ # Initialize general state
64
+ @context = Context.new(self)
65
+ if details.illegal_calls && !details.illegal_calls.empty?
66
+ @tracer = IllegalCallTracer.new(details.illegal_calls)
67
+ end
68
+ @logger = ReplaySafeLogger.new(logger: details.logger, instance: self)
69
+ @logger.scoped_values_getter = proc { scoped_logger_info }
70
+ @runtime_metric_meter = details.metric_meter
71
+ @io_enabled = details.unsafe_workflow_io_enabled
72
+ @scheduler = Scheduler.new(self)
73
+ @payload_converter = details.payload_converter
74
+ @failure_converter = details.failure_converter
75
+ @disable_eager_activity_execution = details.disable_eager_activity_execution
76
+ @pending_activities = {} # Keyed by sequence, value is fiber to resume with proto result
77
+ @pending_timers = {} # Keyed by sequence, value is fiber to resume with proto result
78
+ @pending_child_workflow_starts = {} # Keyed by sequence, value is fiber to resume with proto result
79
+ @pending_child_workflows = {} # Keyed by sequence, value is ChildWorkflowHandle to resolve with proto result
80
+ @pending_external_signals = {} # Keyed by sequence, value is fiber to resume with proto result
81
+ @pending_external_cancels = {} # Keyed by sequence, value is fiber to resume with proto result
82
+ @buffered_signals = {} # Keyed by signal name, value is array of signal jobs
83
+ # TODO(cretz): Should these be sets instead? Both should be fairly low counts.
84
+ @in_progress_handlers = [] # Value is HandlerExecution
85
+ @patches_notified = []
86
+ @definition = details.definition
87
+ @interceptors = details.interceptors
88
+ @cancellation, @cancellation_proc = Cancellation.new
89
+ @continue_as_new_suggested = false
90
+ @current_history_length = 0
91
+ @current_history_size = 0
92
+ @replaying = false
93
+ @failure_exception_types = details.workflow_failure_exception_types + @definition.failure_exception_types
94
+ @signal_handlers = HandlerHash.new(
95
+ details.definition.signals,
96
+ Workflow::Definition::Signal
97
+ ) do |defn|
98
+ # New definition, drain buffer. If it's dynamic (i.e. no name) drain them all.
99
+ to_drain = if defn.name.nil?
100
+ all_signals = @buffered_signals.values.flatten
101
+ @buffered_signals.clear
102
+ all_signals
103
+ else
104
+ @buffered_signals.delete(defn.name)
105
+ end
106
+ to_drain&.each { |job| apply_signal(job) }
107
+ end
108
+ @query_handlers = HandlerHash.new(details.definition.queries, Workflow::Definition::Query)
109
+ @update_handlers = HandlerHash.new(details.definition.updates, Workflow::Definition::Update)
110
+
111
+ # Create all things needed from initial job
112
+ @init_job = details.initial_activation.jobs.find { |j| !j.initialize_workflow.nil? }&.initialize_workflow
113
+ raise 'Missing init job from first activation' unless @init_job
114
+
115
+ illegal_call_tracing_disabled do
116
+ @info = Workflow::Info.new(
117
+ attempt: @init_job.attempt,
118
+ continued_run_id: ProtoUtils.string_or(@init_job.continued_from_execution_run_id),
119
+ cron_schedule: ProtoUtils.string_or(@init_job.cron_schedule),
120
+ execution_timeout: ProtoUtils.duration_to_seconds(@init_job.workflow_execution_timeout),
121
+ headers: ProtoUtils.headers_from_proto_map(@init_job.headers, @payload_converter) || {},
122
+ last_failure: if @init_job.continued_failure
123
+ @failure_converter.from_failure(@init_job.continued_failure, @payload_converter)
124
+ end,
125
+ last_result: if @init_job.last_completion_result
126
+ @payload_converter.from_payloads(@init_job.last_completion_result).first
127
+ end,
128
+ namespace: details.namespace,
129
+ parent: if @init_job.parent_workflow_info
130
+ Workflow::Info::ParentInfo.new(
131
+ namespace: @init_job.parent_workflow_info.namespace,
132
+ run_id: @init_job.parent_workflow_info.run_id,
133
+ workflow_id: @init_job.parent_workflow_info.workflow_id
134
+ )
135
+ end,
136
+ retry_policy: (RetryPolicy._from_proto(@init_job.retry_policy) if @init_job.retry_policy),
137
+ root: if @init_job.root_workflow
138
+ Workflow::Info::RootInfo.new(
139
+ run_id: @init_job.root_workflow.run_id,
140
+ workflow_id: @init_job.root_workflow.workflow_id
141
+ )
142
+ end,
143
+ run_id: details.initial_activation.run_id,
144
+ run_timeout: ProtoUtils.duration_to_seconds(@init_job.workflow_run_timeout),
145
+ start_time: ProtoUtils.timestamp_to_time(details.initial_activation.timestamp) || raise,
146
+ task_queue: details.task_queue,
147
+ task_timeout: ProtoUtils.duration_to_seconds(@init_job.workflow_task_timeout) || raise,
148
+ workflow_id: @init_job.workflow_id,
149
+ workflow_type: @init_job.workflow_type
150
+ ).freeze
151
+
152
+ @random = Random.new(@init_job.randomness_seed)
153
+ end
154
+ end
155
+
156
+ def activate(activation)
157
+ # Run inside of scheduler
158
+ run_in_scheduler { activate_internal(activation) }
159
+ end
160
+
161
+ def add_command(command)
162
+ raise Workflow::InvalidWorkflowStateError, 'Cannot add commands in this context' if @context_frozen
163
+
164
+ @commands << command
165
+ end
166
+
167
+ def instance
168
+ @instance or raise 'Instance accessed before created'
169
+ end
170
+
171
+ def search_attributes
172
+ # Lazy on first access
173
+ @search_attributes ||= SearchAttributes._from_proto(
174
+ @init_job.search_attributes, disable_mutations: true, never_nil: true
175
+ ) || raise
176
+ end
177
+
178
+ def memo
179
+ # Lazy on first access
180
+ @memo ||= ExternallyImmutableHash.new(ProtoUtils.memo_from_proto(@init_job.memo, payload_converter) || {})
181
+ end
182
+
183
+ def now
184
+ # Create each time
185
+ ProtoUtils.timestamp_to_time(@now_timestamp) or raise 'Time unexpectedly not present'
186
+ end
187
+
188
+ def illegal_call_tracing_disabled(&)
189
+ @tracer.disable(&)
190
+ end
191
+
192
+ def patch(patch_id:, deprecated:)
193
+ # Use memoized result if present. If this is being deprecated, we can still use memoized result and skip the
194
+ # command.
195
+ patch_id = patch_id.to_s
196
+ @patches_memoized ||= {}
197
+ @patches_memoized.fetch(patch_id) do
198
+ patched = !replaying || @patches_notified.include?(patch_id)
199
+ @patches_memoized[patch_id] = patched
200
+ if patched
201
+ add_command(
202
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
203
+ set_patch_marker: Bridge::Api::WorkflowCommands::SetPatchMarker.new(patch_id:, deprecated:)
204
+ )
205
+ )
206
+ end
207
+ patched
208
+ end
209
+ end
210
+
211
+ def metric_meter
212
+ @metric_meter ||= ReplaySafeMetric::Meter.new(
213
+ @runtime_metric_meter.with_additional_attributes(
214
+ {
215
+ namespace: info.namespace,
216
+ task_queue: info.task_queue,
217
+ workflow_type: info.workflow_type
218
+ }
219
+ )
220
+ )
221
+ end
222
+
223
+ private
224
+
225
+ def run_in_scheduler(&)
226
+ Fiber.set_scheduler(@scheduler)
227
+ if @tracer
228
+ @tracer.enable(&)
229
+ else
230
+ yield
231
+ end
232
+ ensure
233
+ Fiber.set_scheduler(nil)
234
+ end
235
+
236
+ def activate_internal(activation)
237
+ # Reset some activation state
238
+ @commands = []
239
+ @current_activation_error = nil
240
+ @continue_as_new_suggested = activation.continue_as_new_suggested
241
+ @current_history_length = activation.history_length
242
+ @current_history_size = activation.history_size_bytes
243
+ @replaying = activation.is_replaying
244
+ @now_timestamp = activation.timestamp
245
+
246
+ # Apply jobs and run event loop
247
+ begin
248
+ # Create instance if it doesn't already exist
249
+ @instance ||= with_context_frozen { create_instance }
250
+
251
+ # Apply jobs
252
+ activation.jobs.each { |job| apply(job) }
253
+
254
+ # Schedule primary 'execute' if not already running (i.e. this is
255
+ # the first activation)
256
+ @primary_fiber ||= schedule(top_level: true) { run_workflow }
257
+
258
+ # Run the event loop
259
+ @scheduler.run_until_all_yielded
260
+ rescue Exception => e # rubocop:disable Lint/RescueException
261
+ on_top_level_exception(e)
262
+ end
263
+
264
+ # If we are not replaying and workflow is complete but not a
265
+ # failure (i.e. success, continue as new, or cancel), we warn for
266
+ # any unfinished handlers.
267
+ if !@replaying && @commands.any? do |c|
268
+ !c.complete_workflow_execution.nil? ||
269
+ !c.continue_as_new_workflow_execution.nil? ||
270
+ !c.cancel_workflow_execution.nil?
271
+ end
272
+ warn_on_any_unfinished_handlers
273
+ end
274
+
275
+ # Return success or failure
276
+ if @current_activation_error
277
+ @logger.replay_safety_disabled do
278
+ @logger.warn('Failed activation')
279
+ @logger.warn(@current_activation_error)
280
+ end
281
+ WorkflowInstance.new_completion_with_failure(
282
+ run_id: activation.run_id,
283
+ error: @current_activation_error,
284
+ failure_converter: @failure_converter,
285
+ payload_converter: @payload_converter
286
+ )
287
+ else
288
+ Bridge::Api::WorkflowCompletion::WorkflowActivationCompletion.new(
289
+ run_id: activation.run_id,
290
+ successful: Bridge::Api::WorkflowCompletion::Success.new(commands: @commands)
291
+ )
292
+ end
293
+ ensure
294
+ @commands = nil
295
+ @current_activation_error = nil
296
+ end
297
+
298
+ def create_instance
299
+ # Convert workflow arguments
300
+ @workflow_arguments = convert_args(payload_array: @init_job.arguments,
301
+ method_name: :execute,
302
+ raw_args: @definition.raw_args)
303
+
304
+ # Initialize interceptors
305
+ @inbound = @interceptors.reverse_each.reduce(InboundImplementation.new(self)) do |acc, int|
306
+ int.intercept_workflow(acc)
307
+ end
308
+ @inbound.init(OutboundImplementation.new(self))
309
+
310
+ # Create the user instance
311
+ if @definition.init
312
+ @definition.workflow_class.new(*@workflow_arguments)
313
+ else
314
+ @definition.workflow_class.new
315
+ end
316
+ end
317
+
318
+ def apply(job)
319
+ case job.variant
320
+ when :initialize_workflow
321
+ # Ignore
322
+ when :fire_timer
323
+ pending_timers.delete(job.fire_timer.seq)&.resume
324
+ when :update_random_seed
325
+ @random = illegal_call_tracing_disabled { Random.new(job.update_random_seed.randomness_seed) }
326
+ when :query_workflow
327
+ apply_query(job.query_workflow)
328
+ when :cancel_workflow
329
+ # TODO(cretz): Use the details somehow?
330
+ @cancellation_proc.call(reason: 'Workflow canceled')
331
+ when :signal_workflow
332
+ apply_signal(job.signal_workflow)
333
+ when :resolve_activity
334
+ pending_activities.delete(job.resolve_activity.seq)&.resume(job.resolve_activity.result)
335
+ when :notify_has_patch
336
+ @patches_notified << job.notify_has_patch.patch_id
337
+ when :resolve_child_workflow_execution_start
338
+ pending_child_workflow_starts.delete(job.resolve_child_workflow_execution_start.seq)&.resume(
339
+ job.resolve_child_workflow_execution_start
340
+ )
341
+ when :resolve_child_workflow_execution
342
+ pending_child_workflows.delete(job.resolve_child_workflow_execution.seq)&._resolve(
343
+ job.resolve_child_workflow_execution.result
344
+ )
345
+ when :resolve_signal_external_workflow
346
+ pending_external_signals.delete(job.resolve_signal_external_workflow.seq)&.resume(
347
+ job.resolve_signal_external_workflow
348
+ )
349
+ when :resolve_request_cancel_external_workflow
350
+ pending_external_cancels.delete(job.resolve_request_cancel_external_workflow.seq)&.resume(
351
+ job.resolve_request_cancel_external_workflow
352
+ )
353
+ when :do_update
354
+ apply_update(job.do_update)
355
+ else
356
+ raise "Unrecognized activation job variant: #{job.variant}"
357
+ end
358
+ end
359
+
360
+ def apply_signal(job)
361
+ # Get signal definition, falling back to dynamic if not present and not reserved
362
+ defn = signal_handlers[job.signal_name]
363
+ defn = signal_handlers[nil] if !defn && !Internal::ProtoUtils.reserved_name?(job.signal_name)
364
+
365
+ handler_exec =
366
+ if defn
367
+ HandlerExecution.new(name: job.signal_name, update_id: nil, unfinished_policy: defn.unfinished_policy)
368
+ end
369
+ # Process as a top level handler so that errors are treated as if in primary workflow method
370
+ schedule(top_level: true, handler_exec:) do
371
+ # Send to interceptor if there is a definition, buffer otherwise
372
+ if defn
373
+ @inbound.handle_signal(
374
+ Temporalio::Worker::Interceptor::Workflow::HandleSignalInput.new(
375
+ signal: job.signal_name,
376
+ args: begin
377
+ convert_handler_args(payload_array: job.input, defn:)
378
+ rescue StandardError => e
379
+ # Signals argument conversion failure must not fail task
380
+ @logger.error("Failed converting signal input arguments for #{job.signal_name}, dropping signal")
381
+ @logger.error(e)
382
+ next
383
+ end,
384
+ definition: defn,
385
+ headers: ProtoUtils.headers_from_proto_map(job.headers, @payload_converter) || {}
386
+ )
387
+ )
388
+ else
389
+ buffered = @buffered_signals[job.signal_name]
390
+ buffered = @buffered_signals[job.signal_name] = [] if buffered.nil?
391
+ buffered << job
392
+ end
393
+ end
394
+ end
395
+
396
+ def apply_query(job)
397
+ schedule do
398
+ # If it's a built-in, run it without interceptors, otherwise do normal behavior
399
+ result = if job.query_type == '__stack_trace'
400
+ # Use raw value built from default converter because we don't want to use user-conversion
401
+ Converters::RawValue.new(Converters::PayloadConverter.default.to_payload(scheduler.stack_trace))
402
+ elsif job.query_type == '__temporal_workflow_metadata'
403
+ # Use raw value built from default converter because we don't want to use user-conversion
404
+ Converters::RawValue.new(Converters::PayloadConverter.default.to_payload(workflow_metadata))
405
+ else
406
+ # Get query definition, falling back to dynamic if not present and not reserved
407
+ defn = query_handlers[job.query_type]
408
+ defn = query_handlers[nil] if !defn && !Internal::ProtoUtils.reserved_name?(job.query_type)
409
+
410
+ unless defn
411
+ raise "Query handler for #{job.query_type} expected but not found, " \
412
+ "known queries: [#{query_handlers.keys.compact.sort.join(', ')}]"
413
+ end
414
+
415
+ with_context_frozen do
416
+ @inbound.handle_query(
417
+ Temporalio::Worker::Interceptor::Workflow::HandleQueryInput.new(
418
+ id: job.query_id,
419
+ query: job.query_type,
420
+ args: begin
421
+ convert_handler_args(payload_array: job.arguments, defn:)
422
+ rescue StandardError => e
423
+ raise "Failed converting query input arguments: #{e}"
424
+ end,
425
+ definition: defn,
426
+ headers: ProtoUtils.headers_from_proto_map(job.headers, @payload_converter) || {}
427
+ )
428
+ )
429
+ end
430
+ end
431
+
432
+ add_command(
433
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
434
+ respond_to_query: Bridge::Api::WorkflowCommands::QueryResult.new(
435
+ query_id: job.query_id,
436
+ succeeded: Bridge::Api::WorkflowCommands::QuerySuccess.new(
437
+ response: @payload_converter.to_payload(result)
438
+ )
439
+ )
440
+ )
441
+ )
442
+ rescue Exception => e # rubocop:disable Lint/RescueException
443
+ add_command(
444
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
445
+ respond_to_query: Bridge::Api::WorkflowCommands::QueryResult.new(
446
+ query_id: job.query_id,
447
+ failed: @failure_converter.to_failure(e, @payload_converter)
448
+ )
449
+ )
450
+ )
451
+ end
452
+ end
453
+
454
+ def apply_update(job)
455
+ # Get update definition, falling back to dynamic if not present and not reserved
456
+ defn = update_handlers[job.name]
457
+ defn = update_handlers[nil] if !defn && !Internal::ProtoUtils.reserved_name?(job.name)
458
+
459
+ handler_exec =
460
+ (HandlerExecution.new(name: job.name, update_id: job.id, unfinished_policy: defn.unfinished_policy) if defn)
461
+ schedule(handler_exec:) do
462
+ # Until this is accepted, all errors are rejections
463
+ accepted = false
464
+
465
+ # Set update info
466
+ Fiber[:__temporal_update_info] = Workflow::UpdateInfo.new(id: job.id, name: job.name).freeze
467
+
468
+ # Reject if not present
469
+ unless defn
470
+ raise "Update handler for #{job.name} expected but not found, " \
471
+ "known updates: [#{update_handlers.keys.compact.sort.join(', ')}]"
472
+ end
473
+
474
+ # To match other SDKs, we are only calling the validation interceptor if there is a validator. Also to match
475
+ # other SDKs, we are re-converting the args between validate and update to disallow user mutation in
476
+ # validator/interceptor.
477
+ if job.run_validator && defn.validator_to_invoke
478
+ with_context_frozen do
479
+ @inbound.validate_update(
480
+ Temporalio::Worker::Interceptor::Workflow::HandleUpdateInput.new(
481
+ id: job.id,
482
+ update: job.name,
483
+ args: begin
484
+ convert_handler_args(payload_array: job.input, defn:)
485
+ rescue StandardError => e
486
+ raise "Failed converting update input arguments: #{e}"
487
+ end,
488
+ definition: defn,
489
+ headers: ProtoUtils.headers_from_proto_map(job.headers, @payload_converter) || {}
490
+ )
491
+ )
492
+ end
493
+ end
494
+
495
+ # We build the input before marking accepted so the exception can reject instead of fail task
496
+ input = Temporalio::Worker::Interceptor::Workflow::HandleUpdateInput.new(
497
+ id: job.id,
498
+ update: job.name,
499
+ args: begin
500
+ convert_handler_args(payload_array: job.input, defn:)
501
+ rescue StandardError => e
502
+ raise "Failed converting update input arguments: #{e}"
503
+ end,
504
+ definition: defn,
505
+ headers: ProtoUtils.headers_from_proto_map(job.headers, @payload_converter) || {}
506
+ )
507
+
508
+ # Accept
509
+ add_command(
510
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
511
+ update_response: Bridge::Api::WorkflowCommands::UpdateResponse.new(
512
+ protocol_instance_id: job.protocol_instance_id,
513
+ accepted: Google::Protobuf::Empty.new
514
+ )
515
+ )
516
+ )
517
+ accepted = true
518
+
519
+ # Issue update
520
+ result = @inbound.handle_update(input)
521
+
522
+ add_command(
523
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
524
+ update_response: Bridge::Api::WorkflowCommands::UpdateResponse.new(
525
+ protocol_instance_id: job.protocol_instance_id,
526
+ completed: @payload_converter.to_payload(result)
527
+ )
528
+ )
529
+ )
530
+ rescue Exception => e # rubocop:disable Lint/RescueException
531
+ # Re-raise to cause task failure if this is accepted but this is not a failure exception
532
+ raise if accepted && !failure_exception?(e)
533
+
534
+ # Reject
535
+ add_command(
536
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
537
+ update_response: Bridge::Api::WorkflowCommands::UpdateResponse.new(
538
+ protocol_instance_id: job.protocol_instance_id,
539
+ rejected: @failure_converter.to_failure(e, @payload_converter)
540
+ )
541
+ )
542
+ )
543
+ end
544
+ end
545
+
546
+ def run_workflow
547
+ result = @inbound.execute(
548
+ Temporalio::Worker::Interceptor::Workflow::ExecuteInput.new(
549
+ args: @workflow_arguments,
550
+ headers: @info.headers
551
+ )
552
+ )
553
+ add_command(
554
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
555
+ complete_workflow_execution: Bridge::Api::WorkflowCommands::CompleteWorkflowExecution.new(
556
+ result: @payload_converter.to_payload(result)
557
+ )
558
+ )
559
+ )
560
+ end
561
+
562
+ def schedule(
563
+ top_level: false,
564
+ handler_exec: nil,
565
+ &
566
+ )
567
+ in_progress_handlers << handler_exec if handler_exec
568
+ Fiber.schedule do
569
+ yield
570
+ rescue Exception => e # rubocop:disable Lint/RescueException
571
+ if top_level
572
+ on_top_level_exception(e)
573
+ else
574
+ @current_activation_error ||= e
575
+ end
576
+ ensure
577
+ in_progress_handlers.delete(handler_exec) if handler_exec
578
+ end
579
+ end
580
+
581
+ def on_top_level_exception(err)
582
+ if err.is_a?(Workflow::ContinueAsNewError)
583
+ @logger.debug('Workflow requested continue as new')
584
+ add_command(
585
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
586
+ continue_as_new_workflow_execution: Bridge::Api::WorkflowCommands::ContinueAsNewWorkflowExecution.new(
587
+ workflow_type: if err.workflow
588
+ Workflow::Definition._workflow_type_from_workflow_parameter(err.workflow)
589
+ end,
590
+ task_queue: err.task_queue,
591
+ arguments: ProtoUtils.convert_to_payload_array(payload_converter, err.args),
592
+ workflow_run_timeout: ProtoUtils.seconds_to_duration(err.run_timeout),
593
+ workflow_task_timeout: ProtoUtils.seconds_to_duration(err.task_timeout),
594
+ memo: ProtoUtils.memo_to_proto_hash(err.memo, payload_converter),
595
+ headers: ProtoUtils.headers_to_proto_hash(err.headers, payload_converter),
596
+ search_attributes: err.search_attributes&._to_proto,
597
+ retry_policy: err.retry_policy&._to_proto
598
+ )
599
+ )
600
+ )
601
+ elsif @cancellation.canceled? && Error.canceled?(err)
602
+ # If cancel was ever requested and this is a cancellation or an activity/child cancellation, we add a
603
+ # cancel command. Technically this means that a swallowed cancel followed by, say, an activity cancel
604
+ # later on will show the workflow as cancelled. But this is a Temporal limitation in that cancellation is
605
+ # a state not an event.
606
+ @logger.debug('Workflow requested to cancel and properly raised cancel')
607
+ @logger.debug(err)
608
+ add_command(
609
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
610
+ cancel_workflow_execution: Bridge::Api::WorkflowCommands::CancelWorkflowExecution.new
611
+ )
612
+ )
613
+ elsif failure_exception?(err)
614
+ @logger.debug('Workflow raised failure')
615
+ @logger.debug(err)
616
+ add_command(
617
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
618
+ fail_workflow_execution: Bridge::Api::WorkflowCommands::FailWorkflowExecution.new(
619
+ failure: @failure_converter.to_failure(err, @payload_converter)
620
+ )
621
+ )
622
+ )
623
+ else
624
+ @current_activation_error ||= err
625
+ end
626
+ end
627
+
628
+ def failure_exception?(err)
629
+ err.is_a?(Error::Failure) || err.is_a?(Timeout::Error) || @failure_exception_types.any? do |cls|
630
+ err.is_a?(cls)
631
+ end
632
+ end
633
+
634
+ def with_context_frozen(&)
635
+ @context_frozen = true
636
+ yield
637
+ ensure
638
+ @context_frozen = false
639
+ end
640
+
641
+ def convert_handler_args(payload_array:, defn:)
642
+ convert_args(
643
+ payload_array:,
644
+ method_name: defn.to_invoke.is_a?(Symbol) ? defn.to_invoke : nil,
645
+ raw_args: defn.raw_args,
646
+ ignore_first_param: defn.name.nil? # Dynamic
647
+ )
648
+ end
649
+
650
+ def convert_args(payload_array:, method_name:, raw_args:, ignore_first_param: false)
651
+ # Just in case it is not an array
652
+ payload_array = payload_array.to_ary
653
+
654
+ # We want to discard extra arguments if we can. If there is a method
655
+ # name, try to look it up. Then, assuming there's no :rest, trim args
656
+ # to the amount of :req or :opt there are.
657
+ if method_name && @definition.workflow_class.method_defined?(method_name)
658
+ count = 0
659
+ req_count = 0
660
+ @definition.workflow_class.instance_method(method_name).parameters.each do |(type, _)|
661
+ if type == :rest
662
+ count = nil
663
+ break
664
+ elsif %i[req opt].include?(type)
665
+ count += 1
666
+ req_count += 1 if type == :req
667
+ end
668
+ end
669
+ # Fail if too few required param values, trim off excess if too many. If count is nil, it has a splat.
670
+ if count
671
+ if ignore_first_param
672
+ count -= 1
673
+ req_count -= 1
674
+ end
675
+ if req_count > payload_array.size
676
+ # We have to fail here instead of let Ruby fail the invocation because some handlers, such as signals,
677
+ # want to log and ignore invalid arguments instead of fail and if we used Ruby failure, we can't
678
+ # differentiate between too-few-param caused by us or somewhere else by a user.
679
+ raise ArgumentError, "wrong number of required arguments for #{method_name} " \
680
+ "(given #{payload_array.size}, expected #{req_count})"
681
+ end
682
+ payload_array = payload_array.take(count)
683
+ end
684
+ end
685
+
686
+ # Convert
687
+ if raw_args
688
+ payload_array.map { |p| Converters::RawValue.new(p) }
689
+ else
690
+ ProtoUtils.convert_from_payload_array(@payload_converter, payload_array)
691
+ end
692
+ end
693
+
694
+ def workflow_metadata
695
+ Temporalio::Api::Sdk::V1::WorkflowMetadata.new(
696
+ definition: Temporalio::Api::Sdk::V1::WorkflowDefinition.new(
697
+ type: info.workflow_type,
698
+ query_definitions: query_handlers.values.map do |defn|
699
+ Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new(
700
+ name: defn.name || '', description: defn.description || ''
701
+ )
702
+ end,
703
+ signal_definitions: signal_handlers.values.map do |defn|
704
+ Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new(
705
+ name: defn.name || '', description: defn.description || ''
706
+ )
707
+ end,
708
+ update_definitions: update_handlers.values.map do |defn|
709
+ Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new(
710
+ name: defn.name || '', description: defn.description || ''
711
+ )
712
+ end
713
+ ),
714
+ current_details: current_details || ''
715
+ )
716
+ end
717
+
718
+ def scoped_logger_info
719
+ @scoped_logger_info ||= {
720
+ attempt: info.attempt,
721
+ namespace: info.namespace,
722
+ run_id: info.run_id,
723
+ task_queue: info.task_queue,
724
+ workflow_id: info.workflow_id,
725
+ workflow_type: info.workflow_type
726
+ }
727
+ # Append update info if there is any
728
+ update_info = Fiber[:__temporal_update_info]
729
+ return @scoped_logger_info unless update_info
730
+
731
+ @scoped_logger_info.merge({ update_id: update_info.id, update_name: update_info.name })
732
+ end
733
+
734
+ def warn_on_any_unfinished_handlers
735
+ updates, signals = in_progress_handlers.select do |h|
736
+ h.unfinished_policy == Workflow::HandlerUnfinishedPolicy::WARN_AND_ABANDON
737
+ end.partition(&:update_id)
738
+
739
+ unless updates.empty?
740
+ updates_str = JSON.generate(updates.map { |u| { name: u.name, id: u.update_id } })
741
+ warn(
742
+ "[TMPRL1102] Workflow #{info.workflow_id} finished while update handlers are still running. This may " \
743
+ 'have interrupted work that the update handler was doing, and the client that sent the update will ' \
744
+ "receive a 'workflow execution already completed' RPCError instead of the update result. You can wait " \
745
+ 'for all update and signal handlers to complete by using ' \
746
+ '`Temporalio::Workflow.wait_condition { Temporalio::Workflow.handlers_finished? }`. ' \
747
+ 'Alternatively, if both you and the clients sending the update are okay with interrupting running ' \
748
+ 'handlers when the workflow finishes, and causing clients to receive errors, then you can disable this ' \
749
+ 'warning via the update handler definition: ' \
750
+ '`workflow_update unfinished_policy: Temporalio::Workflow::HandlerUnfinishedPolicy.ABANDON`. ' \
751
+ "The following updates were unfinished (and warnings were not disabled for their handler): #{updates_str}"
752
+ )
753
+ end
754
+
755
+ return if signals.empty?
756
+
757
+ signals_str = JSON.generate(signals.group_by(&:name)
758
+ .transform_values(&:size).sort_by { |_, v| -v }.map { |name, count| { name:, count: } })
759
+ warn(
760
+ "[TMPRL1102] Workflow #{info.workflow_id} finished while signal handlers are still running. This may " \
761
+ 'have interrupted work that the signal handler was doing. You can wait for all update and signal ' \
762
+ 'handlers to complete by using ' \
763
+ '`Temporalio::Workflow.wait_condition { Temporalio::Workflow.handlers_finished? }`. ' \
764
+ 'Alternatively, if both you and the clients sending the signal are okay with interrupting running ' \
765
+ 'handlers when the workflow finishes, then you can disable this warning via the signal handler ' \
766
+ 'definition: ' \
767
+ '`workflow_signal unfinished_policy: Temporalio::Workflow::HandlerUnfinishedPolicy.ABANDON`. ' \
768
+ "The following signals were unfinished (and warnings were not disabled for their handler): #{signals_str}"
769
+ )
770
+ end
771
+ end
772
+ end
773
+ end
774
+ end