clara-temporalio 0.4.3-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) 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/cancellation_details.rb +58 -0
  6. data/lib/temporalio/activity/complete_async_error.rb +11 -0
  7. data/lib/temporalio/activity/context.rb +131 -0
  8. data/lib/temporalio/activity/definition.rb +197 -0
  9. data/lib/temporalio/activity/info.rb +70 -0
  10. data/lib/temporalio/activity.rb +14 -0
  11. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  12. data/lib/temporalio/api/batch/v1/message.rb +38 -0
  13. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  14. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +135 -0
  15. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
  16. data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
  17. data/lib/temporalio/api/cloud/identity/v1/message.rb +46 -0
  18. data/lib/temporalio/api/cloud/namespace/v1/message.rb +46 -0
  19. data/lib/temporalio/api/cloud/nexus/v1/message.rb +32 -0
  20. data/lib/temporalio/api/cloud/operation/v1/message.rb +28 -0
  21. data/lib/temporalio/api/cloud/region/v1/message.rb +24 -0
  22. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  23. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  24. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  25. data/lib/temporalio/api/command/v1/message.rb +46 -0
  26. data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
  27. data/lib/temporalio/api/common/v1/message.rb +49 -0
  28. data/lib/temporalio/api/deployment/v1/message.rb +39 -0
  29. data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
  30. data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
  31. data/lib/temporalio/api/enums/v1/common.rb +28 -0
  32. data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
  33. data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
  34. data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
  35. data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
  36. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  37. data/lib/temporalio/api/enums/v1/query.rb +22 -0
  38. data/lib/temporalio/api/enums/v1/reset.rb +23 -0
  39. data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
  40. data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
  41. data/lib/temporalio/api/enums/v1/update.rb +22 -0
  42. data/lib/temporalio/api/enums/v1/workflow.rb +31 -0
  43. data/lib/temporalio/api/errordetails/v1/message.rb +44 -0
  44. data/lib/temporalio/api/export/v1/message.rb +24 -0
  45. data/lib/temporalio/api/failure/v1/message.rb +38 -0
  46. data/lib/temporalio/api/filter/v1/message.rb +27 -0
  47. data/lib/temporalio/api/history/v1/message.rb +94 -0
  48. data/lib/temporalio/api/namespace/v1/message.rb +31 -0
  49. data/lib/temporalio/api/nexus/v1/message.rb +41 -0
  50. data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
  51. data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
  52. data/lib/temporalio/api/operatorservice.rb +3 -0
  53. data/lib/temporalio/api/payload_visitor.rb +1668 -0
  54. data/lib/temporalio/api/protocol/v1/message.rb +23 -0
  55. data/lib/temporalio/api/query/v1/message.rb +28 -0
  56. data/lib/temporalio/api/replication/v1/message.rb +26 -0
  57. data/lib/temporalio/api/rules/v1/message.rb +27 -0
  58. data/lib/temporalio/api/schedule/v1/message.rb +43 -0
  59. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +25 -0
  60. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +21 -0
  61. data/lib/temporalio/api/sdk/v1/user_metadata.rb +23 -0
  62. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +23 -0
  63. data/lib/temporalio/api/taskqueue/v1/message.rb +48 -0
  64. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  65. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  66. data/lib/temporalio/api/update/v1/message.rb +33 -0
  67. data/lib/temporalio/api/version/v1/message.rb +26 -0
  68. data/lib/temporalio/api/workflow/v1/message.rb +63 -0
  69. data/lib/temporalio/api/workflowservice/v1/request_response.rb +244 -0
  70. data/lib/temporalio/api/workflowservice/v1/service.rb +23 -0
  71. data/lib/temporalio/api/workflowservice.rb +3 -0
  72. data/lib/temporalio/api.rb +15 -0
  73. data/lib/temporalio/cancellation.rb +170 -0
  74. data/lib/temporalio/client/activity_id_reference.rb +32 -0
  75. data/lib/temporalio/client/async_activity_handle.rb +85 -0
  76. data/lib/temporalio/client/connection/cloud_service.rb +786 -0
  77. data/lib/temporalio/client/connection/operator_service.rb +201 -0
  78. data/lib/temporalio/client/connection/service.rb +42 -0
  79. data/lib/temporalio/client/connection/test_service.rb +111 -0
  80. data/lib/temporalio/client/connection/workflow_service.rb +1326 -0
  81. data/lib/temporalio/client/connection.rb +316 -0
  82. data/lib/temporalio/client/interceptor.rb +457 -0
  83. data/lib/temporalio/client/schedule.rb +991 -0
  84. data/lib/temporalio/client/schedule_handle.rb +126 -0
  85. data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
  86. data/lib/temporalio/client/workflow_execution.rb +119 -0
  87. data/lib/temporalio/client/workflow_execution_count.rb +36 -0
  88. data/lib/temporalio/client/workflow_execution_status.rb +18 -0
  89. data/lib/temporalio/client/workflow_handle.rb +389 -0
  90. data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
  91. data/lib/temporalio/client/workflow_update_handle.rb +65 -0
  92. data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
  93. data/lib/temporalio/client.rb +625 -0
  94. data/lib/temporalio/common_enums.rb +55 -0
  95. data/lib/temporalio/contrib/open_telemetry.rb +469 -0
  96. data/lib/temporalio/converters/data_converter.rb +99 -0
  97. data/lib/temporalio/converters/failure_converter.rb +205 -0
  98. data/lib/temporalio/converters/payload_codec.rb +26 -0
  99. data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
  100. data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
  101. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
  102. data/lib/temporalio/converters/payload_converter/composite.rb +66 -0
  103. data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
  104. data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
  105. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
  106. data/lib/temporalio/converters/payload_converter.rb +71 -0
  107. data/lib/temporalio/converters/raw_value.rb +20 -0
  108. data/lib/temporalio/converters.rb +9 -0
  109. data/lib/temporalio/error/failure.rb +237 -0
  110. data/lib/temporalio/error.rb +156 -0
  111. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  112. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  113. data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +0 -0
  114. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
  115. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +32 -0
  116. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
  117. data/lib/temporalio/internal/bridge/api/common/common.rb +27 -0
  118. data/lib/temporalio/internal/bridge/api/core_interface.rb +40 -0
  119. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
  120. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +34 -0
  121. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +56 -0
  122. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +58 -0
  123. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +31 -0
  124. data/lib/temporalio/internal/bridge/api.rb +3 -0
  125. data/lib/temporalio/internal/bridge/client.rb +95 -0
  126. data/lib/temporalio/internal/bridge/runtime.rb +56 -0
  127. data/lib/temporalio/internal/bridge/testing.rb +69 -0
  128. data/lib/temporalio/internal/bridge/worker.rb +109 -0
  129. data/lib/temporalio/internal/bridge.rb +36 -0
  130. data/lib/temporalio/internal/client/implementation.rb +926 -0
  131. data/lib/temporalio/internal/metric.rb +122 -0
  132. data/lib/temporalio/internal/proto_utils.rb +165 -0
  133. data/lib/temporalio/internal/worker/activity_worker.rb +448 -0
  134. data/lib/temporalio/internal/worker/multi_runner.rb +213 -0
  135. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  136. data/lib/temporalio/internal/worker/workflow_instance/context.rb +391 -0
  137. data/lib/temporalio/internal/worker/workflow_instance/details.rb +49 -0
  138. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  139. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  140. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  141. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  142. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  143. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  144. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +404 -0
  145. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  146. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  147. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +183 -0
  148. data/lib/temporalio/internal/worker/workflow_instance.rb +800 -0
  149. data/lib/temporalio/internal/worker/workflow_worker.rb +249 -0
  150. data/lib/temporalio/internal.rb +7 -0
  151. data/lib/temporalio/metric.rb +109 -0
  152. data/lib/temporalio/priority.rb +59 -0
  153. data/lib/temporalio/retry_policy.rb +74 -0
  154. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  155. data/lib/temporalio/runtime.rb +352 -0
  156. data/lib/temporalio/scoped_logger.rb +96 -0
  157. data/lib/temporalio/search_attributes.rb +356 -0
  158. data/lib/temporalio/testing/activity_environment.rb +175 -0
  159. data/lib/temporalio/testing/workflow_environment.rb +406 -0
  160. data/lib/temporalio/testing.rb +10 -0
  161. data/lib/temporalio/version.rb +5 -0
  162. data/lib/temporalio/versioning_override.rb +55 -0
  163. data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
  164. data/lib/temporalio/worker/activity_executor/thread_pool.rb +46 -0
  165. data/lib/temporalio/worker/activity_executor.rb +55 -0
  166. data/lib/temporalio/worker/deployment_options.rb +45 -0
  167. data/lib/temporalio/worker/interceptor.rb +367 -0
  168. data/lib/temporalio/worker/poller_behavior.rb +61 -0
  169. data/lib/temporalio/worker/thread_pool.rb +237 -0
  170. data/lib/temporalio/worker/tuner.rb +189 -0
  171. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +236 -0
  172. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  173. data/lib/temporalio/worker/workflow_replayer.rb +349 -0
  174. data/lib/temporalio/worker.rb +633 -0
  175. data/lib/temporalio/worker_deployment_version.rb +67 -0
  176. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  177. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  178. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  179. data/lib/temporalio/workflow/definition.rb +680 -0
  180. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  181. data/lib/temporalio/workflow/future.rb +151 -0
  182. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  183. data/lib/temporalio/workflow/info.rb +107 -0
  184. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  185. data/lib/temporalio/workflow/update_info.rb +20 -0
  186. data/lib/temporalio/workflow.rb +594 -0
  187. data/lib/temporalio/workflow_history.rb +47 -0
  188. data/lib/temporalio.rb +12 -0
  189. data/temporalio.gemspec +31 -0
  190. metadata +267 -0
@@ -0,0 +1,633 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/activity'
4
+ require 'temporalio/cancellation'
5
+ require 'temporalio/client'
6
+ require 'temporalio/error'
7
+ require 'temporalio/internal/bridge'
8
+ require 'temporalio/internal/bridge/worker'
9
+ require 'temporalio/internal/proto_utils'
10
+ require 'temporalio/internal/worker/activity_worker'
11
+ require 'temporalio/internal/worker/multi_runner'
12
+ require 'temporalio/internal/worker/workflow_instance'
13
+ require 'temporalio/internal/worker/workflow_worker'
14
+ require 'temporalio/worker/activity_executor'
15
+ require 'temporalio/worker/interceptor'
16
+ require 'temporalio/worker/poller_behavior'
17
+ require 'temporalio/worker/thread_pool'
18
+ require 'temporalio/worker/tuner'
19
+ require 'temporalio/worker/workflow_executor'
20
+
21
+ module Temporalio
22
+ # Worker for processing activities and workflows on a task queue.
23
+ #
24
+ # Workers are created for a task queue and the items they can run. Then {run} is used for running a single worker, or
25
+ # {run_all} is used for a collection of workers. These can wait until a block is complete or a {Cancellation} is
26
+ # canceled.
27
+ class Worker
28
+ Options = Data.define(
29
+ :client,
30
+ :task_queue,
31
+ :activities,
32
+ :workflows,
33
+ :tuner,
34
+ :activity_executors,
35
+ :workflow_executor,
36
+ :interceptors,
37
+ :identity,
38
+ :logger,
39
+ :max_cached_workflows,
40
+ :max_concurrent_workflow_task_polls,
41
+ :nonsticky_to_sticky_poll_ratio,
42
+ :max_concurrent_activity_task_polls,
43
+ :no_remote_activities,
44
+ :sticky_queue_schedule_to_start_timeout,
45
+ :max_heartbeat_throttle_interval,
46
+ :default_heartbeat_throttle_interval,
47
+ :max_activities_per_second,
48
+ :max_task_queue_activities_per_second,
49
+ :graceful_shutdown_period,
50
+ :disable_eager_activity_execution,
51
+ :illegal_workflow_calls,
52
+ :workflow_failure_exception_types,
53
+ :workflow_payload_codec_thread_pool,
54
+ :unsafe_workflow_io_enabled,
55
+ :deployment_options,
56
+ :workflow_task_poller_behavior,
57
+ :activity_task_poller_behavior,
58
+ :debug_mode
59
+ )
60
+
61
+ # Options as returned from {options} for `**to_h` splat use in {initialize}. See {initialize} for details.
62
+ #
63
+ # Note, the `client` within can be replaced via client setter.
64
+ class Options; end # rubocop:disable Lint/EmptyClass
65
+
66
+ # @return [String] Memoized default build ID. This default value is built as a checksum of all of the loaded Ruby
67
+ # source files in `$LOADED_FEATURES`. Users may prefer to set the build ID to a better representation of the
68
+ # source.
69
+ def self.default_build_id
70
+ @default_build_id ||= _load_default_build_id
71
+ end
72
+
73
+ # @!visibility private
74
+ def self._load_default_build_id
75
+ # The goal is to get a hash of runtime code, both Temporal's and the
76
+ # user's. After all options were explored, we have decided to default to
77
+ # hashing all bytecode of required files. This means later/dynamic require
78
+ # won't be accounted for because this is memoized. It also means the
79
+ # tiniest code change will affect this, which is what we want since this
80
+ # is meant to be a "binary checksum". We have chosen to use MD5 for speed,
81
+ # similarity with other SDKs, and because security is not a factor.
82
+ require 'digest'
83
+
84
+ saw_bridge = false
85
+ build_id = $LOADED_FEATURES.each_with_object(Digest::MD5.new) do |file, digest|
86
+ saw_bridge = true if file.include?('temporalio_bridge.')
87
+ digest.update(File.read(file)) if File.file?(file)
88
+ end.hexdigest
89
+ raise 'Temporal bridge library not in $LOADED_FEATURES, unable to calculate default build ID' unless saw_bridge
90
+
91
+ build_id
92
+ end
93
+
94
+ # @return [DeploymentOptions] Default deployment options, which does not use worker versioning
95
+ # or a deployment name, and sets the build id to the one from {self.default_build_id}.
96
+ def self.default_deployment_options
97
+ @default_deployment_options ||= DeploymentOptions.new(
98
+ version: WorkerDeploymentVersion.new(deployment_name: '', build_id: Worker.default_build_id)
99
+ )
100
+ end
101
+
102
+ # Run all workers until cancellation or optional block completes. When the cancellation or block is complete, the
103
+ # workers are shut down. This will return the block result if everything successful or raise an error if not. See
104
+ # {run} for details on how worker shutdown works.
105
+ #
106
+ # @param workers [Array<Worker>] Workers to run.
107
+ # @param cancellation [Cancellation] Cancellation that can be canceled to shut down all workers.
108
+ # @param shutdown_signals [Array] Signals to trap and cause worker shutdown.
109
+ # @param raise_in_block_on_shutdown [Exception, nil] Exception to {::Thread.raise} or {::Fiber.raise} if a block is
110
+ # present and still running on shutdown. If nil, `raise` is not used.
111
+ # @param wait_block_complete [Boolean] If block given and shutdown caused by something else (e.g. cancellation
112
+ # canceled), whether to wait on the block to complete before returning.
113
+ # @yield Optional block. This will be run in a new background thread or fiber. Workers will shut down upon
114
+ # completion of this and, assuming no other failures, return/bubble success/exception of the block.
115
+ # @return [Object] Return value of the block or nil of no block given.
116
+ def self.run_all(
117
+ *workers,
118
+ cancellation: Cancellation.new,
119
+ shutdown_signals: [],
120
+ raise_in_block_on_shutdown: Error::CanceledError.new('Workers finished'),
121
+ wait_block_complete: true,
122
+ &block
123
+ )
124
+ # Confirm there is at least one and they are all workers
125
+ raise ArgumentError, 'At least one worker required' if workers.empty?
126
+ raise ArgumentError, 'Not all parameters are workers' unless workers.all? { |w| w.is_a?(Worker) }
127
+
128
+ Internal::Bridge.assert_fiber_compatibility!
129
+
130
+ # Start the multi runner
131
+ runner = Internal::Worker::MultiRunner.new(workers:, shutdown_signals:)
132
+
133
+ # Apply block
134
+ runner.apply_thread_or_fiber_block(&block)
135
+
136
+ # Reuse first worker logger
137
+ logger = workers.first&.options&.logger or raise # Never nil
138
+
139
+ # On cancel, initiate shutdown
140
+ cancellation.add_cancel_callback do
141
+ logger.info('Cancel invoked, beginning worker shutdown')
142
+ runner.initiate_shutdown
143
+ end
144
+
145
+ # Poller loop, run until all pollers shut down
146
+ first_error = nil
147
+ block_result = nil
148
+ loop do
149
+ event = runner.next_event
150
+ # TODO(cretz): Consider improving performance instead of this case statement
151
+ case event
152
+ when Internal::Worker::MultiRunner::Event::PollSuccess
153
+ # Successful poll
154
+ event.worker #: Worker
155
+ ._on_poll_bytes(runner, event.worker_type, event.bytes)
156
+ when Internal::Worker::MultiRunner::Event::PollFailure
157
+ # Poll failure, this causes shutdown of all workers
158
+ logger.error('Poll failure (beginning worker shutdown if not already occurring)')
159
+ logger.error(event.error)
160
+ first_error ||= event.error
161
+ runner.initiate_shutdown
162
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationDecoded
163
+ # Came back from a codec as decoded
164
+ event.workflow_worker.handle_activation(runner:, activation: event.activation, decoded: true)
165
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationComplete
166
+ # An activation is complete
167
+ event.workflow_worker.handle_activation_complete(
168
+ runner:,
169
+ activation_completion: event.activation_completion,
170
+ encoded: event.encoded,
171
+ completion_complete_queue: event.completion_complete_queue
172
+ )
173
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationCompletionComplete
174
+ # Completion complete, only need to log error if it occurs here
175
+ if event.error
176
+ logger.error("Activation completion failed to record on run ID #{event.run_id}")
177
+ logger.error(event.error)
178
+ end
179
+ when Internal::Worker::MultiRunner::Event::PollerShutDown
180
+ # Individual poller shut down. Nothing to do here until we support
181
+ # worker status or something.
182
+ when Internal::Worker::MultiRunner::Event::AllPollersShutDown
183
+ # This is where we break the loop, no more polling can happen
184
+ break
185
+ when Internal::Worker::MultiRunner::Event::BlockSuccess
186
+ logger.info('Block completed, beginning worker shutdown')
187
+ block_result = event
188
+ runner.initiate_shutdown
189
+ when Internal::Worker::MultiRunner::Event::BlockFailure
190
+ logger.error('Block failure (beginning worker shutdown)')
191
+ logger.error(event.error)
192
+ block_result = event
193
+ first_error ||= event.error
194
+ runner.initiate_shutdown
195
+ when Internal::Worker::MultiRunner::Event::ShutdownSignalReceived
196
+ logger.info('Signal received, beginning worker shutdown')
197
+ runner.initiate_shutdown
198
+ else
199
+ raise "Unexpected event: #{event}"
200
+ end
201
+ end
202
+
203
+ # Now that all pollers have stopped, let's wait for all to complete
204
+ begin
205
+ runner.wait_complete_and_finalize_shutdown
206
+ rescue StandardError => e
207
+ logger.warn('Failed waiting and finalizing')
208
+ logger.warn(e)
209
+ end
210
+
211
+ # If there was a block but not a result yet, we want to raise if that is
212
+ # wanted, and wait if that is wanted
213
+ if block_given? && block_result.nil?
214
+ runner.raise_in_thread_or_fiber_block(raise_in_block_on_shutdown) unless raise_in_block_on_shutdown.nil?
215
+ if wait_block_complete
216
+ event = runner.next_event
217
+ case event
218
+ when Internal::Worker::MultiRunner::Event::BlockSuccess
219
+ logger.info('Block completed (after worker shutdown)')
220
+ block_result = event
221
+ when Internal::Worker::MultiRunner::Event::BlockFailure
222
+ logger.error('Block failure (after worker shutdown)')
223
+ logger.error(event.error)
224
+ block_result = event
225
+ first_error ||= event.error
226
+ when Internal::Worker::MultiRunner::Event::ShutdownSignalReceived
227
+ # Do nothing, waiting for block
228
+ else
229
+ raise "Unexpected event: #{event}"
230
+ end
231
+ end
232
+ end
233
+
234
+ # Notify each worker we're done with it
235
+ workers.each(&:_on_shutdown_complete)
236
+
237
+ # If there was an shutdown-causing error, we raise that
238
+ if !first_error.nil?
239
+ raise first_error
240
+ elsif block_result.is_a?(Internal::Worker::MultiRunner::Event::BlockSuccess)
241
+ block_result.result
242
+ end
243
+ end
244
+
245
+ # @return [Hash<String, [:all, Array<Symbol>]>] Default, immutable set illegal calls used for the
246
+ # `illegal_workflow_calls` worker option. See the documentation of that option for more details.
247
+ def self.default_illegal_workflow_calls
248
+ @default_illegal_workflow_calls ||= begin
249
+ hash = {
250
+ 'BasicSocket' => :all,
251
+ 'Date' => %i[initialize today],
252
+ 'DateTime' => %i[initialize now],
253
+ 'Dir' => :all,
254
+ 'Fiber' => [:set_scheduler],
255
+ 'File' => :all,
256
+ 'FileTest' => :all,
257
+ 'FileUtils' => :all,
258
+ 'Find' => :all,
259
+ 'GC' => :all,
260
+ 'IO' => [
261
+ :read
262
+ # Intentionally leaving out write so puts will work. We don't want to add heavy logic replacing stdout or
263
+ # trying to derive whether it's file vs stdout write.
264
+ #:write
265
+ ],
266
+ 'Kernel' => %i[abort at_exit autoload autoload? eval exec exit fork gets load open rand readline readlines
267
+ spawn srand system test trap],
268
+ 'Net::HTTP' => :all,
269
+ 'Pathname' => :all,
270
+ # TODO(cretz): Investigate why clock_gettime called from Timeout thread affects this code at all. Stack trace
271
+ # test executing activities inside a timeout will fail if clock_gettime is blocked.
272
+ 'Process' => %i[abort argv0 daemon detach exec exit exit! fork kill setpriority setproctitle setrlimit setsid
273
+ spawn times wait wait2 waitall warmup],
274
+ # TODO(cretz): Allow Ractor.current since exception formatting in error_highlight references it
275
+ # 'Ractor' => :all,
276
+ 'Random::Base' => [:initialize],
277
+ 'Resolv' => :all,
278
+ 'SecureRandom' => :all,
279
+ 'Signal' => :all,
280
+ 'Socket' => :all,
281
+ 'Tempfile' => :all,
282
+ 'Thread' => %i[abort_on_exception= exit fork handle_interrupt ignore_deadlock= kill new pass
283
+ pending_interrupt? report_on_exception= start stop initialize join name= priority= raise run
284
+ terminate thread_variable_set wakeup],
285
+ 'Time' => %i[initialize now]
286
+ } #: Hash[String, :all | Array[Symbol]]
287
+ hash.each_value(&:freeze)
288
+ hash.freeze
289
+ end
290
+ end
291
+
292
+ # @return [Options] Options for this worker which has the same attributes as {initialize}.
293
+ attr_reader :options
294
+
295
+ # Create a new worker. At least one activity or workflow must be present.
296
+ #
297
+ # @param client [Client] Client for this worker.
298
+ # @param task_queue [String] Task queue for this worker.
299
+ # @param activities [Array<Activity::Definition, Class<Activity::Definition>, Activity::Definition::Info>]
300
+ # Activities for this worker.
301
+ # @param workflows [Array<Class<Workflow::Definition>>] Workflows for this worker.
302
+ # @param tuner [Tuner] Tuner that controls the amount of concurrent activities/workflows that run at a time.
303
+ # @param activity_executors [Hash<Symbol, Worker::ActivityExecutor>] Executors that activities can run within.
304
+ # @param workflow_executor [WorkflowExecutor] Workflow executor that workflow tasks run within. This must be a
305
+ # {WorkflowExecutor::ThreadPool} currently.
306
+ # @param interceptors [Array<Interceptor::Activity, Interceptor::Workflow>] Interceptors specific to this worker.
307
+ # Note, interceptors set on the client that include the {Interceptor::Activity} or {Interceptor::Workflow} module
308
+ # are automatically included here, so no need to specify them again.
309
+ # @param identity [String, nil] Override the identity for this worker. If unset, client identity is used.
310
+ # @param logger [Logger] Logger to override client logger with. Default is the client logger.
311
+ # @param max_cached_workflows [Integer] Number of workflows held in cache for use by sticky task queue. If set to 0,
312
+ # workflow caching and sticky queuing are disabled.
313
+ # @param max_concurrent_workflow_task_polls [Integer] Maximum number of concurrent poll workflow task requests we
314
+ # will perform at a time on this worker's task queue.
315
+ # @param nonsticky_to_sticky_poll_ratio [Float] `max_concurrent_workflow_task_polls` * this number = the number of
316
+ # max pollers that will be allowed for the nonsticky queue when sticky tasks are enabled. If both defaults are
317
+ # used, the sticky queue will allow 4 max pollers while the nonsticky queue will allow one. The minimum for either
318
+ # poller is 1, so if `max_concurrent_workflow_task_polls` is 1 and sticky queues are enabled, there will be 2
319
+ # concurrent polls.
320
+ # @param max_concurrent_activity_task_polls [Integer] Maximum number of concurrent poll activity task requests we
321
+ # will perform at a time on this worker's task queue.
322
+ # @param no_remote_activities [Boolean] If true, this worker will only handle workflow tasks and local activities,
323
+ # it will not poll for activity tasks.
324
+ # @param sticky_queue_schedule_to_start_timeout [Float] How long a workflow task is allowed to sit on the sticky
325
+ # queue before it is timed out and moved to the non-sticky queue where it may be picked up by any worker.
326
+ # @param max_heartbeat_throttle_interval [Float] Longest interval for throttling activity heartbeats.
327
+ # @param default_heartbeat_throttle_interval [Float] Default interval for throttling activity heartbeats in case
328
+ # per-activity heartbeat timeout is unset. Otherwise, it's the per-activity heartbeat timeout * 0.8.
329
+ # @param max_activities_per_second [Float, nil] Limits the number of activities per second that this worker will
330
+ # process. The worker will not poll for new activities if by doing so it might receive and execute an activity
331
+ # which would cause it to exceed this limit.
332
+ # @param max_task_queue_activities_per_second [Float, nil] Sets the maximum number of activities per second the task
333
+ # queue will dispatch, controlled server-side. Note that this only takes effect upon an activity poll request. If
334
+ # multiple workers on the same queue have different values set, they will thrash with the last poller winning.
335
+ # @param graceful_shutdown_period [Float] Amount of time after shutdown is called that activities are given to
336
+ # complete before their tasks are canceled.
337
+ # @param disable_eager_activity_execution [Boolean] If true, disables eager activity execution. Eager activity
338
+ # execution is an optimization on some servers that sends activities back to the same worker as the calling
339
+ # workflow if they can run there. This should be set to true for `max_task_queue_activities_per_second` to work
340
+ # and in a future version of this API may be implied as such (i.e. this setting will be ignored if that setting is
341
+ # set).
342
+ # @param illegal_workflow_calls [Hash<String, [:all, Array<Symbol>]>] Set of illegal workflow calls that are
343
+ # considered unsafe/non-deterministic and will raise if seen. The key of the hash is the fully qualified string
344
+ # class name (no leading `::`). The value is either `:all` which means any use of the class, or an array of
345
+ # symbols for methods on the class that cannot be used. The methods refer to either instance or class methods,
346
+ # there is no way to differentiate at this time.
347
+ # @param workflow_failure_exception_types [Array<Class<Exception>>] Workflow failure exception types. This is the
348
+ # set of exception types that, if a workflow-thrown exception extends, will cause the workflow/update to fail
349
+ # instead of suspending the workflow via task failure. These are applied in addition to the
350
+ # `workflow_failure_exception_type` on the workflow definition class itself. If {::Exception} is set, it
351
+ # effectively will fail a workflow/update in all user exception cases.
352
+ # @param workflow_payload_codec_thread_pool [ThreadPool, nil] Thread pool to run payload codec encode/decode within.
353
+ # This is required if a payload codec exists and the worker is not fiber based. Codecs can potentially block
354
+ # execution which is why they need to be run in the background.
355
+ # @param unsafe_workflow_io_enabled [Boolean] If false, the default, workflow code that invokes io_wait on the fiber
356
+ # scheduler will fail. Instead of setting this to true, users are encouraged to use {Workflow::Unsafe.io_enabled}
357
+ # with a block for narrower enabling of IO.
358
+ # @param deployment_options [DeploymentOptions, nil] Deployment options for the worker.
359
+ # WARNING: This is an experimental feature and may change in the future.
360
+ # @param workflow_task_poller_behavior [PollerBehavior] Specify the behavior of workflow task
361
+ # polling. Defaults to a 5-poller maximum.
362
+ # @param activity_task_poller_behavior [PollerBehavior] Specify the behavior of activity task
363
+ # polling. Defaults to a 5-poller maximum.
364
+ # @param debug_mode [Boolean] If true, deadlock detection is disabled. Deadlock detection will fail workflow tasks
365
+ # if they block the thread for too long. This defaults to true if the `TEMPORAL_DEBUG` environment variable is
366
+ # `true` or `1`.
367
+ def initialize(
368
+ client:,
369
+ task_queue:,
370
+ activities: [],
371
+ workflows: [],
372
+ tuner: Tuner.create_fixed,
373
+ activity_executors: ActivityExecutor.defaults,
374
+ workflow_executor: WorkflowExecutor::ThreadPool.default,
375
+ interceptors: [],
376
+ identity: nil,
377
+ logger: client.options.logger,
378
+ max_cached_workflows: 1000,
379
+ max_concurrent_workflow_task_polls: 5,
380
+ nonsticky_to_sticky_poll_ratio: 0.2,
381
+ max_concurrent_activity_task_polls: 5,
382
+ no_remote_activities: false,
383
+ sticky_queue_schedule_to_start_timeout: 10,
384
+ max_heartbeat_throttle_interval: 60,
385
+ default_heartbeat_throttle_interval: 30,
386
+ max_activities_per_second: nil,
387
+ max_task_queue_activities_per_second: nil,
388
+ graceful_shutdown_period: 0,
389
+ disable_eager_activity_execution: false,
390
+ illegal_workflow_calls: Worker.default_illegal_workflow_calls,
391
+ workflow_failure_exception_types: [],
392
+ workflow_payload_codec_thread_pool: nil,
393
+ unsafe_workflow_io_enabled: false,
394
+ deployment_options: Worker.default_deployment_options,
395
+ workflow_task_poller_behavior: PollerBehavior::SimpleMaximum.new(max_concurrent_workflow_task_polls),
396
+ activity_task_poller_behavior: PollerBehavior::SimpleMaximum.new(max_concurrent_activity_task_polls),
397
+ debug_mode: %w[true 1].include?(ENV['TEMPORAL_DEBUG'].to_s.downcase)
398
+ )
399
+ raise ArgumentError, 'Must have at least one activity or workflow' if activities.empty? && workflows.empty?
400
+
401
+ Internal::ProtoUtils.assert_non_reserved_name(task_queue)
402
+
403
+ @options = Options.new(
404
+ client:,
405
+ task_queue:,
406
+ activities:,
407
+ workflows:,
408
+ tuner:,
409
+ activity_executors:,
410
+ workflow_executor:,
411
+ interceptors:,
412
+ identity:,
413
+ logger:,
414
+ max_cached_workflows:,
415
+ max_concurrent_workflow_task_polls:,
416
+ nonsticky_to_sticky_poll_ratio:,
417
+ max_concurrent_activity_task_polls:,
418
+ no_remote_activities:,
419
+ sticky_queue_schedule_to_start_timeout:,
420
+ max_heartbeat_throttle_interval:,
421
+ default_heartbeat_throttle_interval:,
422
+ max_activities_per_second:,
423
+ max_task_queue_activities_per_second:,
424
+ graceful_shutdown_period:,
425
+ disable_eager_activity_execution:,
426
+ illegal_workflow_calls:,
427
+ workflow_failure_exception_types:,
428
+ workflow_payload_codec_thread_pool:,
429
+ unsafe_workflow_io_enabled:,
430
+ deployment_options:,
431
+ workflow_task_poller_behavior:,
432
+ activity_task_poller_behavior:,
433
+ debug_mode:
434
+ ).freeze
435
+
436
+ should_enforce_versioning_behavior =
437
+ deployment_options.use_worker_versioning &&
438
+ deployment_options.default_versioning_behavior == VersioningBehavior::UNSPECIFIED
439
+ # Preload workflow definitions and some workflow settings for the bridge
440
+ workflow_definitions = Internal::Worker::WorkflowWorker.workflow_definitions(
441
+ workflows,
442
+ should_enforce_versioning_behavior: should_enforce_versioning_behavior
443
+ )
444
+ nondeterminism_as_workflow_fail, nondeterminism_as_workflow_fail_for_types =
445
+ Internal::Worker::WorkflowWorker.bridge_workflow_failure_exception_type_options(
446
+ workflow_failure_exception_types:, workflow_definitions:
447
+ )
448
+
449
+ # Create the bridge worker
450
+ @bridge_worker = Internal::Bridge::Worker.new(
451
+ client.connection._core_client,
452
+ Internal::Bridge::Worker::Options.new(
453
+ activity: !activities.empty?,
454
+ workflow: !workflows.empty?,
455
+ namespace: client.namespace,
456
+ task_queue:,
457
+ tuner: tuner._to_bridge_options,
458
+ identity_override: identity,
459
+ max_cached_workflows:,
460
+ workflow_task_poller_behavior: workflow_task_poller_behavior._to_bridge_options,
461
+ nonsticky_to_sticky_poll_ratio:,
462
+ activity_task_poller_behavior: activity_task_poller_behavior._to_bridge_options,
463
+ # For shutdown to work properly, we must disable remote activities
464
+ # ourselves if there are no activities
465
+ no_remote_activities: no_remote_activities || activities.empty?,
466
+ sticky_queue_schedule_to_start_timeout:,
467
+ max_heartbeat_throttle_interval:,
468
+ default_heartbeat_throttle_interval:,
469
+ max_worker_activities_per_second: max_activities_per_second,
470
+ max_task_queue_activities_per_second:,
471
+ graceful_shutdown_period:,
472
+ nondeterminism_as_workflow_fail:,
473
+ nondeterminism_as_workflow_fail_for_types:,
474
+ deployment_options: deployment_options._to_bridge_options
475
+ )
476
+ )
477
+
478
+ # Collect interceptors from client and params
479
+ @activity_interceptors = (client.options.interceptors + interceptors).select do |i|
480
+ i.is_a?(Interceptor::Activity)
481
+ end
482
+ @workflow_interceptors = (client.options.interceptors + interceptors).select do |i|
483
+ i.is_a?(Interceptor::Workflow)
484
+ end
485
+
486
+ # Cancellation for the whole worker
487
+ @worker_shutdown_cancellation = Cancellation.new
488
+
489
+ # Create workers
490
+ unless activities.empty?
491
+ @activity_worker = Internal::Worker::ActivityWorker.new(worker: self,
492
+ bridge_worker: @bridge_worker)
493
+ end
494
+ unless workflows.empty?
495
+ @workflow_worker = Internal::Worker::WorkflowWorker.new(
496
+ bridge_worker: @bridge_worker,
497
+ namespace: client.namespace,
498
+ task_queue:,
499
+ workflow_definitions:,
500
+ workflow_executor:,
501
+ logger:,
502
+ data_converter: client.data_converter,
503
+ metric_meter: client.connection.options.runtime.metric_meter,
504
+ workflow_interceptors: @workflow_interceptors,
505
+ disable_eager_activity_execution:,
506
+ illegal_workflow_calls:,
507
+ workflow_failure_exception_types:,
508
+ workflow_payload_codec_thread_pool:,
509
+ unsafe_workflow_io_enabled:,
510
+ debug_mode:,
511
+ assert_valid_local_activity: ->(activity) { _assert_valid_local_activity(activity) }
512
+ )
513
+ end
514
+
515
+ # Validate worker
516
+ @bridge_worker.validate
517
+
518
+ # Mutex needed for accessing and replacing a client
519
+ @client_mutex = Mutex.new
520
+ end
521
+
522
+ # @return [String] Task queue set on the worker options.
523
+ def task_queue
524
+ @options.task_queue
525
+ end
526
+
527
+ # @return [Client] Client for this worker. This is the same as {Options.client} in {options}, but surrounded by a
528
+ # mutex to be safe for client replacement in {client=}.
529
+ def client
530
+ @client_mutex.synchronize { @options.client }
531
+ end
532
+
533
+ # Replace the worker's client. When this is called, the client is replaced on the internal worker which means any
534
+ # new calls will be made on the new client (but existing calls will still complete on the previous one). This is
535
+ # commonly used for providing a new client with updated authentication credentials.
536
+ #
537
+ # @param new_client [Client] New client to use for new calls.
538
+ def client=(new_client)
539
+ @client_mutex.synchronize do
540
+ @bridge_worker.replace_client(new_client.connection._core_client)
541
+ @options = @options.with(client: new_client)
542
+ new_client
543
+ end
544
+ end
545
+
546
+ # Run this worker until cancellation or optional block completes. When the cancellation or block is complete, the
547
+ # worker is shut down. This will return the block result if everything successful or raise an error if not.
548
+ #
549
+ # Upon shutdown (either via cancellation, block completion, or worker fatal error), the worker immediately stops
550
+ # accepting new work. Then, after an optional grace period, all activities are canceled. This call then waits for
551
+ # every activity and workflow task to complete before returning.
552
+ #
553
+ # @param cancellation [Cancellation] Cancellation that can be canceled to shut down this worker.
554
+ # @param shutdown_signals [Array] Signals to trap and cause worker shutdown.
555
+ # @param raise_in_block_on_shutdown [Exception, nil] Exception to {::Thread.raise} or {::Fiber.raise} if a block is
556
+ # present and still running on shutdown. If nil, `raise` is not used.
557
+ # @param wait_block_complete [Boolean] If block given and shutdown caused by something else (e.g. cancellation
558
+ # canceled), whether to wait on the block to complete before returning.
559
+ # @yield Optional block. This will be run in a new background thread or fiber. Worker will shut down upon completion
560
+ # of this and, assuming no other failures, return/bubble success/exception of the block.
561
+ # @return [Object] Return value of the block or nil of no block given.
562
+ def run(
563
+ cancellation: Cancellation.new,
564
+ shutdown_signals: [],
565
+ raise_in_block_on_shutdown: Error::CanceledError.new('Workers finished'),
566
+ wait_block_complete: true,
567
+ &block
568
+ )
569
+ Worker.run_all(self, cancellation:, shutdown_signals:, raise_in_block_on_shutdown:, wait_block_complete:, &block)
570
+ end
571
+
572
+ # @!visibility private
573
+ def _worker_shutdown_cancellation
574
+ @worker_shutdown_cancellation
575
+ end
576
+
577
+ # @!visibility private
578
+ def _initiate_shutdown
579
+ _bridge_worker.initiate_shutdown
580
+ _, cancel_proc = _worker_shutdown_cancellation
581
+ cancel_proc.call
582
+ end
583
+
584
+ # @!visibility private
585
+ def _wait_all_complete
586
+ @activity_worker&.wait_all_complete
587
+ end
588
+
589
+ # @!visibility private
590
+ def _bridge_worker
591
+ @bridge_worker
592
+ end
593
+
594
+ # @!visibility private
595
+ def _activity_interceptors
596
+ @activity_interceptors
597
+ end
598
+
599
+ # @!visibility private
600
+ def _on_poll_bytes(runner, worker_type, bytes)
601
+ case worker_type
602
+ when :activity
603
+ @activity_worker.handle_task(Internal::Bridge::Api::ActivityTask::ActivityTask.decode(bytes))
604
+ when :workflow
605
+ @workflow_worker.handle_activation(
606
+ runner:,
607
+ activation: Internal::Bridge::Api::WorkflowActivation::WorkflowActivation.decode(bytes),
608
+ decoded: false
609
+ )
610
+ else
611
+ raise "Unrecognized worker type #{worker_type}"
612
+ end
613
+ end
614
+
615
+ # @!visibility private
616
+ def _on_shutdown_complete
617
+ @workflow_worker&.on_shutdown_complete
618
+ @workflow_worker = nil
619
+ end
620
+
621
+ # @!visibility private
622
+ def _assert_valid_local_activity(activity)
623
+ unless @activity_worker.nil?
624
+ @activity_worker.assert_valid_activity(activity)
625
+ return
626
+ end
627
+
628
+ raise ArgumentError,
629
+ "Activity #{activity} " \
630
+ 'is not registered on this worker, no available activities.'
631
+ end
632
+ end
633
+ end