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
@@ -6,11 +6,16 @@ require 'temporalio/client'
6
6
  require 'temporalio/error'
7
7
  require 'temporalio/internal/bridge'
8
8
  require 'temporalio/internal/bridge/worker'
9
+ require 'temporalio/internal/proto_utils'
9
10
  require 'temporalio/internal/worker/activity_worker'
10
11
  require 'temporalio/internal/worker/multi_runner'
12
+ require 'temporalio/internal/worker/workflow_instance'
13
+ require 'temporalio/internal/worker/workflow_worker'
11
14
  require 'temporalio/worker/activity_executor'
12
15
  require 'temporalio/worker/interceptor'
16
+ require 'temporalio/worker/thread_pool'
13
17
  require 'temporalio/worker/tuner'
18
+ require 'temporalio/worker/workflow_executor'
14
19
 
15
20
  module Temporalio
16
21
  # Worker for processing activities and workflows on a task queue.
@@ -19,13 +24,14 @@ module Temporalio
19
24
  # {run_all} is used for a collection of workers. These can wait until a block is complete or a {Cancellation} is
20
25
  # canceled.
21
26
  class Worker
22
- # Options as returned from {options} for `**to_h`` splat use in {initialize}. See {initialize} for details.
23
- Options = Struct.new(
27
+ Options = Data.define(
24
28
  :client,
25
29
  :task_queue,
26
30
  :activities,
27
- :activity_executors,
31
+ :workflows,
28
32
  :tuner,
33
+ :activity_executors,
34
+ :workflow_executor,
29
35
  :interceptors,
30
36
  :build_id,
31
37
  :identity,
@@ -42,9 +48,19 @@ module Temporalio
42
48
  :max_task_queue_activities_per_second,
43
49
  :graceful_shutdown_period,
44
50
  :use_worker_versioning,
45
- keyword_init: true
51
+ :disable_eager_activity_execution,
52
+ :illegal_workflow_calls,
53
+ :workflow_failure_exception_types,
54
+ :workflow_payload_codec_thread_pool,
55
+ :unsafe_workflow_io_enabled,
56
+ :debug_mode
46
57
  )
47
58
 
59
+ # Options as returned from {options} for `**to_h` splat use in {initialize}. See {initialize} for details.
60
+ #
61
+ # Note, the `client` within can be replaced via client setter.
62
+ class Options; end # rubocop:disable Lint/EmptyClass
63
+
48
64
  # @return [String] Memoized default build ID. This default value is built as a checksum of all of the loaded Ruby
49
65
  # source files in `$LOADED_FEATURES`. Users may prefer to set the build ID to a better representation of the
50
66
  # source.
@@ -108,7 +124,7 @@ module Temporalio
108
124
  runner.apply_thread_or_fiber_block(&block)
109
125
 
110
126
  # Reuse first worker logger
111
- logger = workers.first&.options&.logger or raise # Help steep
127
+ logger = workers.first&.options&.logger or raise # Never nil
112
128
 
113
129
  # On cancel, initiate shutdown
114
130
  cancellation.add_cancel_callback do
@@ -121,16 +137,35 @@ module Temporalio
121
137
  block_result = nil
122
138
  loop do
123
139
  event = runner.next_event
140
+ # TODO(cretz): Consider improving performance instead of this case statement
124
141
  case event
125
142
  when Internal::Worker::MultiRunner::Event::PollSuccess
126
143
  # Successful poll
127
- event.worker._on_poll_bytes(event.worker_type, event.bytes)
144
+ event.worker #: Worker
145
+ ._on_poll_bytes(runner, event.worker_type, event.bytes)
128
146
  when Internal::Worker::MultiRunner::Event::PollFailure
129
147
  # Poll failure, this causes shutdown of all workers
130
- logger.error('Poll failure (beginning worker shutdown if not alaredy occurring)')
148
+ logger.error('Poll failure (beginning worker shutdown if not already occurring)')
131
149
  logger.error(event.error)
132
150
  first_error ||= event.error
133
151
  runner.initiate_shutdown
152
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationDecoded
153
+ # Came back from a codec as decoded
154
+ event.workflow_worker.handle_activation(runner:, activation: event.activation, decoded: true)
155
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationComplete
156
+ # An activation is complete
157
+ event.workflow_worker.handle_activation_complete(
158
+ runner:,
159
+ activation_completion: event.activation_completion,
160
+ encoded: event.encoded,
161
+ completion_complete_queue: event.completion_complete_queue
162
+ )
163
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationCompletionComplete
164
+ # Completion complete, only need to log error if it occurs here
165
+ if event.error
166
+ logger.error("Activation completion failed to record on run ID #{event.run_id}")
167
+ logger.error(event.error)
168
+ end
134
169
  when Internal::Worker::MultiRunner::Event::PollerShutDown
135
170
  # Individual poller shut down. Nothing to do here until we support
136
171
  # worker status or something.
@@ -186,6 +221,9 @@ module Temporalio
186
221
  end
187
222
  end
188
223
 
224
+ # Notify each worker we're done with it
225
+ workers.each(&:_on_shutdown_complete)
226
+
189
227
  # If there was an shutdown-causing error, we raise that
190
228
  if !first_error.nil?
191
229
  raise first_error
@@ -194,27 +232,80 @@ module Temporalio
194
232
  end
195
233
  end
196
234
 
197
- # @return [Options] Frozen options for this client which has the same attributes as {initialize}.
235
+ # @return [Hash<String, [:all, Array<Symbol>]>] Default, immutable set illegal calls used for the
236
+ # `illegal_workflow_calls` worker option. See the documentation of that option for more details.
237
+ def self.default_illegal_workflow_calls
238
+ @default_illegal_workflow_calls ||= begin
239
+ hash = {
240
+ 'BasicSocket' => :all,
241
+ 'Date' => %i[initialize today],
242
+ 'DateTime' => %i[initialize now],
243
+ 'Dir' => :all,
244
+ 'Fiber' => [:set_scheduler],
245
+ 'File' => :all,
246
+ 'FileTest' => :all,
247
+ 'FileUtils' => :all,
248
+ 'Find' => :all,
249
+ 'GC' => :all,
250
+ 'IO' => [
251
+ :read
252
+ # Intentionally leaving out write so puts will work. We don't want to add heavy logic replacing stdout or
253
+ # trying to derive whether it's file vs stdout write.
254
+ #:write
255
+ ],
256
+ 'Kernel' => %i[abort at_exit autoload autoload? eval exec exit fork gets load open rand readline readlines
257
+ spawn srand system test trap],
258
+ 'Net::HTTP' => :all,
259
+ 'Pathname' => :all,
260
+ # TODO(cretz): Investigate why clock_gettime called from Timeout thread affects this code at all. Stack trace
261
+ # test executing activities inside a timeout will fail if clock_gettime is blocked.
262
+ 'Process' => %i[abort argv0 daemon detach exec exit exit! fork kill setpriority setproctitle setrlimit setsid
263
+ spawn times wait wait2 waitall warmup],
264
+ # TODO(cretz): Allow Ractor.current since exception formatting in error_highlight references it
265
+ # 'Ractor' => :all,
266
+ 'Random::Base' => [:initialize],
267
+ 'Resolv' => :all,
268
+ 'SecureRandom' => :all,
269
+ 'Signal' => :all,
270
+ 'Socket' => :all,
271
+ 'Tempfile' => :all,
272
+ 'Thread' => %i[abort_on_exception= exit fork handle_interrupt ignore_deadlock= kill new pass
273
+ pending_interrupt? report_on_exception= start stop initialize join name= priority= raise run
274
+ terminate thread_variable_set wakeup],
275
+ 'Time' => %i[initialize now]
276
+ } #: Hash[String, :all | Array[Symbol]]
277
+ hash.each_value(&:freeze)
278
+ hash.freeze
279
+ end
280
+ end
281
+
282
+ # @return [Options] Options for this worker which has the same attributes as {initialize}.
198
283
  attr_reader :options
199
284
 
200
285
  # Create a new worker. At least one activity or workflow must be present.
201
286
  #
202
287
  # @param client [Client] Client for this worker.
203
288
  # @param task_queue [String] Task queue for this worker.
204
- # @param activities [Array<Activity, Class<Activity>, Activity::Definition>] Activities for this worker.
205
- # @param activity_executors [Hash<Symbol, Worker::ActivityExecutor>] Executors that activities can run within.
289
+ # @param activities [Array<Activity::Definition, Class<Activity::Definition>, Activity::Definition::Info>]
290
+ # Activities for this worker.
291
+ # @param workflows [Array<Class<Workflow::Definition>>] Workflows for this worker.
206
292
  # @param tuner [Tuner] Tuner that controls the amount of concurrent activities/workflows that run at a time.
207
- # @param interceptors [Array<Interceptor>] Interceptors specific to this worker. Note, interceptors set on the
208
- # client that include the {Interceptor} module are automatically included here, so no need to specify them again.
293
+ # @param activity_executors [Hash<Symbol, Worker::ActivityExecutor>] Executors that activities can run within.
294
+ # @param workflow_executor [WorkflowExecutor] Workflow executor that workflow tasks run within. This must be a
295
+ # {WorkflowExecutor::ThreadPool} currently.
296
+ # @param interceptors [Array<Interceptor::Activity, Interceptor::Workflow>] Interceptors specific to this worker.
297
+ # Note, interceptors set on the client that include the {Interceptor::Activity} or {Interceptor::Workflow} module
298
+ # are automatically included here, so no need to specify them again.
209
299
  # @param build_id [String] Unique identifier for the current runtime. This is best set as a unique value
210
300
  # representing all code and should change only when code does. This can be something like a git commit hash. If
211
301
  # unset, default is hash of known Ruby code.
212
302
  # @param identity [String, nil] Override the identity for this worker. If unset, client identity is used.
303
+ # @param logger [Logger] Logger to override client logger with. Default is the client logger.
213
304
  # @param max_cached_workflows [Integer] Number of workflows held in cache for use by sticky task queue. If set to 0,
214
305
  # workflow caching and sticky queuing are disabled.
215
306
  # @param max_concurrent_workflow_task_polls [Integer] Maximum number of concurrent poll workflow task requests we
216
307
  # will perform at a time on this worker's task queue.
217
- # @param nonsticky_to_sticky_poll_ratio [Float] `max_concurrent_workflow_task_polls`` * this number = the number of
308
+ # @param nonsticky_to_sticky_poll_ratio [Float] `max_concurrent_workflow_task_polls` * this number = the number of
218
309
  # max pollers that will be allowed for the nonsticky queue when sticky tasks are enabled. If both defaults are
219
310
  # used, the sticky queue will allow 4 max pollers while the nonsticky queue will allow one. The minimum for either
220
311
  # poller is 1, so if `max_concurrent_workflow_task_polls` is 1 and sticky queues are enabled, there will be 2
@@ -239,12 +330,38 @@ module Temporalio
239
330
  # @param use_worker_versioning [Boolean] If true, the `build_id` argument must be specified, and this worker opts
240
331
  # into the worker versioning feature. This ensures it only receives workflow tasks for workflows which it claims
241
332
  # to be compatible with. For more information, see https://docs.temporal.io/workers#worker-versioning.
333
+ # @param disable_eager_activity_execution [Boolean] If true, disables eager activity execution. Eager activity
334
+ # execution is an optimization on some servers that sends activities back to the same worker as the calling
335
+ # workflow if they can run there. This should be set to true for `max_task_queue_activities_per_second` to work
336
+ # and in a future version of this API may be implied as such (i.e. this setting will be ignored if that setting is
337
+ # set).
338
+ # @param illegal_workflow_calls [Hash<String, [:all, Array<Symbol>]>] Set of illegal workflow calls that are
339
+ # considered unsafe/non-deterministic and will raise if seen. The key of the hash is the fully qualified string
340
+ # class name (no leading `::`). The value is either `:all` which means any use of the class, or an array of
341
+ # symbols for methods on the class that cannot be used. The methods refer to either instance or class methods,
342
+ # there is no way to differentiate at this time.
343
+ # @param workflow_failure_exception_types [Array<Class<Exception>>] Workflow failure exception types. This is the
344
+ # set of exception types that, if a workflow-thrown exception extends, will cause the workflow/update to fail
345
+ # instead of suspending the workflow via task failure. These are applied in addition to the
346
+ # `workflow_failure_exception_type` on the workflow definition class itself. If {::Exception} is set, it
347
+ # effectively will fail a workflow/update in all user exception cases.
348
+ # @param workflow_payload_codec_thread_pool [ThreadPool, nil] Thread pool to run payload codec encode/decode within.
349
+ # This is required if a payload codec exists and the worker is not fiber based. Codecs can potentially block
350
+ # execution which is why they need to be run in the background.
351
+ # @param unsafe_workflow_io_enabled [Boolean] If false, the default, workflow code that invokes io_wait on the fiber
352
+ # scheduler will fail. Instead of setting this to true, users are encouraged to use {Workflow::Unsafe.io_enabled}
353
+ # with a block for narrower enabling of IO.
354
+ # @param debug_mode [Boolean] If true, deadlock detection is disabled. Deadlock detection will fail workflow tasks
355
+ # if they block the thread for too long. This defaults to true if the `TEMPORAL_DEBUG` environment variable is
356
+ # `true` or `1`.
242
357
  def initialize(
243
358
  client:,
244
359
  task_queue:,
245
360
  activities: [],
246
- activity_executors: ActivityExecutor.defaults,
361
+ workflows: [],
247
362
  tuner: Tuner.create_fixed,
363
+ activity_executors: ActivityExecutor.defaults,
364
+ workflow_executor: WorkflowExecutor::ThreadPool.default,
248
365
  interceptors: [],
249
366
  build_id: Worker.default_build_id,
250
367
  identity: nil,
@@ -260,17 +377,26 @@ module Temporalio
260
377
  max_activities_per_second: nil,
261
378
  max_task_queue_activities_per_second: nil,
262
379
  graceful_shutdown_period: 0,
263
- use_worker_versioning: false
380
+ use_worker_versioning: false,
381
+ disable_eager_activity_execution: false,
382
+ illegal_workflow_calls: Worker.default_illegal_workflow_calls,
383
+ workflow_failure_exception_types: [],
384
+ workflow_payload_codec_thread_pool: nil,
385
+ unsafe_workflow_io_enabled: false,
386
+ debug_mode: %w[true 1].include?(ENV['TEMPORAL_DEBUG'].to_s.downcase)
264
387
  )
265
- # TODO(cretz): Remove when workflows come about
266
- raise ArgumentError, 'Must have at least one activity' if activities.empty?
388
+ raise ArgumentError, 'Must have at least one activity or workflow' if activities.empty? && workflows.empty?
389
+
390
+ Internal::ProtoUtils.assert_non_reserved_name(task_queue)
267
391
 
268
392
  @options = Options.new(
269
393
  client:,
270
394
  task_queue:,
271
395
  activities:,
272
- activity_executors:,
396
+ workflows:,
273
397
  tuner:,
398
+ activity_executors:,
399
+ workflow_executor:,
274
400
  interceptors:,
275
401
  build_id:,
276
402
  identity:,
@@ -286,51 +412,93 @@ module Temporalio
286
412
  max_activities_per_second:,
287
413
  max_task_queue_activities_per_second:,
288
414
  graceful_shutdown_period:,
289
- use_worker_versioning:
415
+ use_worker_versioning:,
416
+ disable_eager_activity_execution:,
417
+ illegal_workflow_calls:,
418
+ workflow_failure_exception_types:,
419
+ workflow_payload_codec_thread_pool:,
420
+ unsafe_workflow_io_enabled:,
421
+ debug_mode:
290
422
  ).freeze
291
423
 
424
+ # Preload workflow definitions and some workflow settings for the bridge
425
+ workflow_definitions = Internal::Worker::WorkflowWorker.workflow_definitions(workflows)
426
+ nondeterminism_as_workflow_fail, nondeterminism_as_workflow_fail_for_types =
427
+ Internal::Worker::WorkflowWorker.bridge_workflow_failure_exception_type_options(
428
+ workflow_failure_exception_types:, workflow_definitions:
429
+ )
430
+
292
431
  # Create the bridge worker
293
432
  @bridge_worker = Internal::Bridge::Worker.new(
294
433
  client.connection._core_client,
295
434
  Internal::Bridge::Worker::Options.new(
296
435
  activity: !activities.empty?,
297
- workflow: false,
436
+ workflow: !workflows.empty?,
298
437
  namespace: client.namespace,
299
438
  task_queue:,
300
- tuner: Internal::Bridge::Worker::TunerOptions.new(
301
- workflow_slot_supplier: to_bridge_slot_supplier_options(tuner.workflow_slot_supplier),
302
- activity_slot_supplier: to_bridge_slot_supplier_options(tuner.activity_slot_supplier),
303
- local_activity_slot_supplier: to_bridge_slot_supplier_options(tuner.local_activity_slot_supplier)
304
- ),
439
+ tuner: tuner._to_bridge_options,
305
440
  build_id:,
306
441
  identity_override: identity,
307
442
  max_cached_workflows:,
308
443
  max_concurrent_workflow_task_polls:,
309
444
  nonsticky_to_sticky_poll_ratio:,
310
445
  max_concurrent_activity_task_polls:,
311
- no_remote_activities:,
446
+ # For shutdown to work properly, we must disable remote activities
447
+ # ourselves if there are no activities
448
+ no_remote_activities: no_remote_activities || activities.empty?,
312
449
  sticky_queue_schedule_to_start_timeout:,
313
450
  max_heartbeat_throttle_interval:,
314
451
  default_heartbeat_throttle_interval:,
315
452
  max_worker_activities_per_second: max_activities_per_second,
316
453
  max_task_queue_activities_per_second:,
317
454
  graceful_shutdown_period:,
318
- use_worker_versioning:
455
+ use_worker_versioning:,
456
+ nondeterminism_as_workflow_fail:,
457
+ nondeterminism_as_workflow_fail_for_types:
319
458
  )
320
459
  )
321
460
 
322
461
  # Collect interceptors from client and params
323
- @all_interceptors = client.options.interceptors.select { |i| i.is_a?(Interceptor) } + interceptors
462
+ @activity_interceptors = (client.options.interceptors + interceptors).select do |i|
463
+ i.is_a?(Interceptor::Activity)
464
+ end
465
+ @workflow_interceptors = (client.options.interceptors + interceptors).select do |i|
466
+ i.is_a?(Interceptor::Workflow)
467
+ end
324
468
 
325
469
  # Cancellation for the whole worker
326
470
  @worker_shutdown_cancellation = Cancellation.new
327
471
 
328
472
  # Create workers
329
- # TODO(cretz): Make conditional when workflows appear
330
- @activity_worker = Internal::Worker::ActivityWorker.new(self, @bridge_worker)
473
+ unless activities.empty?
474
+ @activity_worker = Internal::Worker::ActivityWorker.new(worker: self,
475
+ bridge_worker: @bridge_worker)
476
+ end
477
+ unless workflows.empty?
478
+ @workflow_worker = Internal::Worker::WorkflowWorker.new(
479
+ bridge_worker: @bridge_worker,
480
+ namespace: client.namespace,
481
+ task_queue:,
482
+ workflow_definitions:,
483
+ workflow_executor:,
484
+ logger:,
485
+ data_converter: client.data_converter,
486
+ metric_meter: client.connection.options.runtime.metric_meter,
487
+ workflow_interceptors: @workflow_interceptors,
488
+ disable_eager_activity_execution:,
489
+ illegal_workflow_calls:,
490
+ workflow_failure_exception_types:,
491
+ workflow_payload_codec_thread_pool:,
492
+ unsafe_workflow_io_enabled:,
493
+ debug_mode:
494
+ )
495
+ end
331
496
 
332
497
  # Validate worker
333
498
  @bridge_worker.validate
499
+
500
+ # Mutex needed for accessing and replacing a client
501
+ @client_mutex = Mutex.new
334
502
  end
335
503
 
336
504
  # @return [String] Task queue set on the worker options.
@@ -338,6 +506,25 @@ module Temporalio
338
506
  @options.task_queue
339
507
  end
340
508
 
509
+ # @return [Client] Client for this worker. This is the same as {Options.client} in {options}, but surrounded by a
510
+ # mutex to be safe for client replacement in {client=}.
511
+ def client
512
+ @client_mutex.synchronize { @options.client }
513
+ end
514
+
515
+ # Replace the worker's client. When this is called, the client is replaced on the internal worker which means any
516
+ # new calls will be made on the new client (but existing calls will still complete on the previous one). This is
517
+ # commonly used for providing a new client with updated authentication credentials.
518
+ #
519
+ # @param new_client [Client] New client to use for new calls.
520
+ def client=(new_client)
521
+ @client_mutex.synchronize do
522
+ @bridge_worker.replace_client(new_client.connection._core_client)
523
+ @options = @options.with(client: new_client)
524
+ new_client
525
+ end
526
+ end
527
+
341
528
  # Run this worker until cancellation or optional block completes. When the cancellation or block is complete, the
342
529
  # worker is shut down. This will return the block result if everything successful or raise an error if not.
343
530
  #
@@ -387,40 +574,30 @@ module Temporalio
387
574
  end
388
575
 
389
576
  # @!visibility private
390
- def _all_interceptors
391
- @all_interceptors
577
+ def _activity_interceptors
578
+ @activity_interceptors
392
579
  end
393
580
 
394
581
  # @!visibility private
395
- def _on_poll_bytes(worker_type, bytes)
396
- # TODO(cretz): Workflow workers
397
- raise "Unrecognized worker type #{worker_type}" unless worker_type == :activity
398
-
399
- @activity_worker.handle_task(Internal::Bridge::Api::ActivityTask::ActivityTask.decode(bytes))
400
- end
401
-
402
- private
403
-
404
- def to_bridge_slot_supplier_options(slot_supplier)
405
- if slot_supplier.is_a?(Tuner::SlotSupplier::Fixed)
406
- Internal::Bridge::Worker::TunerSlotSupplierOptions.new(
407
- fixed_size: slot_supplier.slots,
408
- resource_based: nil
409
- )
410
- elsif slot_supplier.is_a?(Tuner::SlotSupplier::ResourceBased)
411
- Internal::Bridge::Worker::TunerSlotSupplierOptions.new(
412
- fixed_size: nil,
413
- resource_based: Internal::Bridge::Worker::TunerResourceBasedSlotSupplierOptions.new(
414
- target_mem_usage: slot_supplier.tuner_options.target_memory_usage,
415
- target_cpu_usage: slot_supplier.tuner_options.target_cpu_usage,
416
- min_slots: slot_supplier.slot_options.min_slots,
417
- max_slots: slot_supplier.slot_options.max_slots,
418
- ramp_throttle: slot_supplier.slot_options.ramp_throttle
419
- )
582
+ def _on_poll_bytes(runner, worker_type, bytes)
583
+ case worker_type
584
+ when :activity
585
+ @activity_worker.handle_task(Internal::Bridge::Api::ActivityTask::ActivityTask.decode(bytes))
586
+ when :workflow
587
+ @workflow_worker.handle_activation(
588
+ runner:,
589
+ activation: Internal::Bridge::Api::WorkflowActivation::WorkflowActivation.decode(bytes),
590
+ decoded: false
420
591
  )
421
592
  else
422
- raise ArgumentError, 'Tuner slot suppliers must be instances of Fixed or ResourceBased'
593
+ raise "Unrecognized worker type #{worker_type}"
423
594
  end
424
595
  end
596
+
597
+ # @!visibility private
598
+ def _on_shutdown_complete
599
+ @workflow_worker&.on_shutdown_complete
600
+ @workflow_worker = nil
601
+ end
425
602
  end
426
603
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/internal/bridge/api'
4
+
5
+ module Temporalio
6
+ module Workflow
7
+ # Cancellation types for activities.
8
+ module ActivityCancellationType
9
+ # Initiate a cancellation request and immediately report cancellation to the workflow.
10
+ TRY_CANCEL = Internal::Bridge::Api::WorkflowCommands::ActivityCancellationType::TRY_CANCEL
11
+ # Wait for activity cancellation completion. Note that activity must heartbeat to receive a cancellation
12
+ # notification. This can block the cancellation for a long time if activity doesn't heartbeat or chooses to ignore
13
+ # the cancellation request.
14
+ WAIT_CANCELLATION_COMPLETED =
15
+ Internal::Bridge::Api::WorkflowCommands::ActivityCancellationType::WAIT_CANCELLATION_COMPLETED
16
+ # Do not request cancellation of the activity and immediately report cancellation to the workflow.
17
+ ABANDON = Internal::Bridge::Api::WorkflowCommands::ActivityCancellationType::ABANDON
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/internal/bridge/api'
4
+
5
+ module Temporalio
6
+ module Workflow
7
+ # Cancellation types for child workflows.
8
+ module ChildWorkflowCancellationType
9
+ # Do not request cancellation of the child workflow if already scheduled.
10
+ ABANDON = Internal::Bridge::Api::ChildWorkflow::ChildWorkflowCancellationType::ABANDON
11
+ # Initiate a cancellation request and immediately report cancellation to the parent.
12
+ TRY_CANCEL = Internal::Bridge::Api::ChildWorkflow::ChildWorkflowCancellationType::TRY_CANCEL
13
+ # Wait for child cancellation completion.
14
+ WAIT_CANCELLATION_COMPLETED =
15
+ Internal::Bridge::Api::ChildWorkflow::ChildWorkflowCancellationType::WAIT_CANCELLATION_COMPLETED
16
+ # Request cancellation of the child and wait for confirmation that the request was received.
17
+ WAIT_CANCELLATION_REQUESTED =
18
+ Internal::Bridge::Api::ChildWorkflow::ChildWorkflowCancellationType::WAIT_CANCELLATION_REQUESTED
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ module Workflow
5
+ # Handle for interacting with a child workflow.
6
+ #
7
+ # This is created via {Workflow.start_child_workflow}, it is never instantiated directly.
8
+ class ChildWorkflowHandle
9
+ # @!visibility private
10
+ def initialize
11
+ raise NotImplementedError, 'Cannot instantiate a child handle directly'
12
+ end
13
+
14
+ # @return [String] ID for the workflow.
15
+ def id
16
+ raise NotImplementedError
17
+ end
18
+
19
+ # @return [String] Run ID for the workflow.
20
+ def first_execution_run_id
21
+ raise NotImplementedError
22
+ end
23
+
24
+ # Wait for the result.
25
+ #
26
+ # @return [Object] Result of the child workflow.
27
+ #
28
+ # @raise [Error::ChildWorkflowError] Workflow failed with +cause+ as the cause.
29
+ def result
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # Signal the child workflow.
34
+ #
35
+ # @param signal [Workflow::Definition::Signal, Symbol, String] Signal definition or name.
36
+ # @param args [Array<Object>] Signal args.
37
+ # @param cancellation [Cancellation] Cancellation for canceling the signalling.
38
+ def signal(signal, *args, cancellation: Workflow.cancellation)
39
+ raise NotImplementedError
40
+ end
41
+ end
42
+ end
43
+ end