temporalio 0.3.0-x86_64-linux → 0.5.0-x86_64-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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/Gemfile +4 -0
  4. data/Rakefile +1 -1
  5. data/lib/temporalio/activity/cancellation_details.rb +58 -0
  6. data/lib/temporalio/activity/context.rb +23 -1
  7. data/lib/temporalio/activity/definition.rb +63 -8
  8. data/lib/temporalio/activity/info.rb +28 -4
  9. data/lib/temporalio/activity.rb +2 -0
  10. data/lib/temporalio/api/activity/v1/message.rb +1 -1
  11. data/lib/temporalio/api/batch/v1/message.rb +9 -2
  12. data/lib/temporalio/api/cloud/account/v1/message.rb +1 -1
  13. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +11 -2
  14. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +2 -2
  15. data/lib/temporalio/api/cloud/identity/v1/message.rb +7 -2
  16. data/lib/temporalio/api/cloud/namespace/v1/message.rb +6 -2
  17. data/lib/temporalio/api/cloud/nexus/v1/message.rb +3 -2
  18. data/lib/temporalio/api/cloud/operation/v1/message.rb +1 -1
  19. data/lib/temporalio/api/cloud/region/v1/message.rb +1 -1
  20. data/lib/temporalio/api/cloud/resource/v1/message.rb +1 -1
  21. data/lib/temporalio/api/cloud/sink/v1/message.rb +1 -1
  22. data/lib/temporalio/api/cloud/usage/v1/message.rb +1 -1
  23. data/lib/temporalio/api/command/v1/message.rb +2 -2
  24. data/lib/temporalio/api/common/v1/grpc_status.rb +1 -1
  25. data/lib/temporalio/api/common/v1/message.rb +4 -2
  26. data/lib/temporalio/api/deployment/v1/message.rb +39 -0
  27. data/lib/temporalio/api/enums/v1/batch_operation.rb +2 -2
  28. data/lib/temporalio/api/enums/v1/command_type.rb +1 -1
  29. data/lib/temporalio/api/enums/v1/common.rb +5 -2
  30. data/lib/temporalio/api/enums/v1/deployment.rb +24 -0
  31. data/lib/temporalio/api/enums/v1/event_type.rb +2 -2
  32. data/lib/temporalio/api/enums/v1/failed_cause.rb +2 -2
  33. data/lib/temporalio/api/enums/v1/namespace.rb +1 -1
  34. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  35. data/lib/temporalio/api/enums/v1/query.rb +1 -1
  36. data/lib/temporalio/api/enums/v1/reset.rb +2 -2
  37. data/lib/temporalio/api/enums/v1/schedule.rb +1 -1
  38. data/lib/temporalio/api/enums/v1/task_queue.rb +1 -1
  39. data/lib/temporalio/api/enums/v1/update.rb +1 -1
  40. data/lib/temporalio/api/enums/v1/workflow.rb +3 -2
  41. data/lib/temporalio/api/errordetails/v1/message.rb +4 -2
  42. data/lib/temporalio/api/export/v1/message.rb +1 -1
  43. data/lib/temporalio/api/failure/v1/message.rb +5 -2
  44. data/lib/temporalio/api/filter/v1/message.rb +1 -1
  45. data/lib/temporalio/api/history/v1/message.rb +6 -2
  46. data/lib/temporalio/api/namespace/v1/message.rb +1 -1
  47. data/lib/temporalio/api/nexus/v1/message.rb +3 -2
  48. data/lib/temporalio/api/operatorservice/v1/request_response.rb +1 -1
  49. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  50. data/lib/temporalio/api/payload_visitor.rb +162 -7
  51. data/lib/temporalio/api/protocol/v1/message.rb +1 -1
  52. data/lib/temporalio/api/query/v1/message.rb +3 -2
  53. data/lib/temporalio/api/replication/v1/message.rb +1 -1
  54. data/lib/temporalio/api/rules/v1/message.rb +27 -0
  55. data/lib/temporalio/api/schedule/v1/message.rb +2 -2
  56. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +1 -1
  57. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +1 -1
  58. data/lib/temporalio/api/sdk/v1/user_metadata.rb +1 -1
  59. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +1 -1
  60. data/lib/temporalio/api/taskqueue/v1/message.rb +5 -2
  61. data/lib/temporalio/api/testservice/v1/request_response.rb +1 -1
  62. data/lib/temporalio/api/testservice/v1/service.rb +1 -1
  63. data/lib/temporalio/api/update/v1/message.rb +1 -1
  64. data/lib/temporalio/api/version/v1/message.rb +1 -1
  65. data/lib/temporalio/api/worker/v1/message.rb +30 -0
  66. data/lib/temporalio/api/workflow/v1/message.rb +22 -2
  67. data/lib/temporalio/api/workflowservice/v1/request_response.rb +58 -12
  68. data/lib/temporalio/api/workflowservice/v1/service.rb +2 -2
  69. data/lib/temporalio/api.rb +1 -0
  70. data/lib/temporalio/client/async_activity_handle.rb +12 -4
  71. data/lib/temporalio/client/connection/cloud_service.rb +60 -0
  72. data/lib/temporalio/client/connection/workflow_service.rb +343 -28
  73. data/lib/temporalio/client/interceptor.rb +64 -7
  74. data/lib/temporalio/client/schedule.rb +35 -3
  75. data/lib/temporalio/client/with_start_workflow_operation.rb +123 -0
  76. data/lib/temporalio/client/workflow_execution.rb +19 -0
  77. data/lib/temporalio/client/workflow_handle.rb +47 -7
  78. data/lib/temporalio/client/workflow_update_handle.rb +9 -3
  79. data/lib/temporalio/client.rb +231 -4
  80. data/lib/temporalio/common_enums.rb +14 -0
  81. data/lib/temporalio/contrib/open_telemetry.rb +474 -0
  82. data/lib/temporalio/converters/data_converter.rb +18 -8
  83. data/lib/temporalio/converters/failure_converter.rb +6 -3
  84. data/lib/temporalio/converters/payload_converter/binary_null.rb +2 -2
  85. data/lib/temporalio/converters/payload_converter/binary_plain.rb +2 -2
  86. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +2 -2
  87. data/lib/temporalio/converters/payload_converter/composite.rb +6 -4
  88. data/lib/temporalio/converters/payload_converter/encoding.rb +4 -2
  89. data/lib/temporalio/converters/payload_converter/json_plain.rb +2 -2
  90. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +2 -2
  91. data/lib/temporalio/converters/payload_converter.rb +16 -6
  92. data/lib/temporalio/error/failure.rb +19 -1
  93. data/lib/temporalio/error.rb +2 -1
  94. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  95. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  96. data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +0 -0
  97. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +1 -1
  98. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -2
  99. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +1 -1
  100. data/lib/temporalio/internal/bridge/api/common/common.rb +3 -2
  101. data/lib/temporalio/internal/bridge/api/core_interface.rb +1 -1
  102. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +1 -1
  103. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +3 -2
  104. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -2
  105. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +3 -2
  106. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +3 -2
  107. data/lib/temporalio/internal/bridge/runtime.rb +3 -0
  108. data/lib/temporalio/internal/bridge/testing.rb +3 -0
  109. data/lib/temporalio/internal/bridge/worker.rb +28 -4
  110. data/lib/temporalio/internal/bridge.rb +1 -1
  111. data/lib/temporalio/internal/client/implementation.rb +281 -51
  112. data/lib/temporalio/internal/proto_utils.rb +38 -6
  113. data/lib/temporalio/internal/worker/activity_worker.rb +112 -27
  114. data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
  115. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +8 -6
  116. data/lib/temporalio/internal/worker/workflow_instance/context.rb +100 -5
  117. data/lib/temporalio/internal/worker/workflow_instance/details.rb +7 -2
  118. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +2 -2
  119. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +64 -18
  120. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +39 -40
  121. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
  122. data/lib/temporalio/internal/worker/workflow_instance.rb +134 -55
  123. data/lib/temporalio/internal/worker/workflow_worker.rb +74 -21
  124. data/lib/temporalio/priority.rb +59 -0
  125. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  126. data/lib/temporalio/runtime.rb +48 -10
  127. data/lib/temporalio/search_attributes.rb +13 -0
  128. data/lib/temporalio/testing/activity_environment.rb +59 -16
  129. data/lib/temporalio/testing/workflow_environment.rb +29 -6
  130. data/lib/temporalio/version.rb +1 -1
  131. data/lib/temporalio/versioning_override.rb +56 -0
  132. data/lib/temporalio/worker/deployment_options.rb +45 -0
  133. data/lib/temporalio/worker/illegal_workflow_call_validator.rb +64 -0
  134. data/lib/temporalio/worker/interceptor.rb +16 -1
  135. data/lib/temporalio/worker/poller_behavior.rb +61 -0
  136. data/lib/temporalio/worker/thread_pool.rb +6 -6
  137. data/lib/temporalio/worker/tuner.rb +38 -0
  138. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +14 -8
  139. data/lib/temporalio/worker/workflow_executor.rb +1 -1
  140. data/lib/temporalio/worker/workflow_replayer.rb +349 -0
  141. data/lib/temporalio/worker.rb +117 -75
  142. data/lib/temporalio/worker_deployment_version.rb +67 -0
  143. data/lib/temporalio/workflow/child_workflow_handle.rb +10 -2
  144. data/lib/temporalio/workflow/definition.rb +217 -35
  145. data/lib/temporalio/workflow/external_workflow_handle.rb +3 -1
  146. data/lib/temporalio/workflow/future.rb +2 -2
  147. data/lib/temporalio/workflow/info.rb +26 -1
  148. data/lib/temporalio/workflow.rb +119 -15
  149. data/lib/temporalio/workflow_history.rb +26 -1
  150. data/lib/temporalio.rb +1 -0
  151. data/temporalio.gemspec +3 -1
  152. metadata +34 -4
@@ -22,6 +22,7 @@ module Temporalio
22
22
  ExecuteInput = Data.define(
23
23
  :proc,
24
24
  :args,
25
+ :result_hint,
25
26
  :headers
26
27
  )
27
28
 
@@ -59,7 +60,8 @@ module Temporalio
59
60
 
60
61
  # Input for {Outbound.heartbeat}.
61
62
  HeartbeatInput = Data.define(
62
- :details
63
+ :details,
64
+ :detail_hints
63
65
  )
64
66
 
65
67
  # Outbound interceptor for intercepting outbound activity calls. This should be extended by users needing to
@@ -206,6 +208,7 @@ module Temporalio
206
208
  :activity,
207
209
  :args,
208
210
  :task_queue,
211
+ :summary,
209
212
  :schedule_to_close_timeout,
210
213
  :schedule_to_start_timeout,
211
214
  :start_to_close_timeout,
@@ -215,6 +218,9 @@ module Temporalio
215
218
  :cancellation_type,
216
219
  :activity_id,
217
220
  :disable_eager_execution,
221
+ :priority,
222
+ :arg_hints,
223
+ :result_hint,
218
224
  :headers
219
225
  )
220
226
 
@@ -230,6 +236,8 @@ module Temporalio
230
236
  :cancellation,
231
237
  :cancellation_type,
232
238
  :activity_id,
239
+ :arg_hints,
240
+ :result_hint,
233
241
  :headers
234
242
  )
235
243
 
@@ -244,6 +252,7 @@ module Temporalio
244
252
  :signal,
245
253
  :args,
246
254
  :cancellation,
255
+ :arg_hints,
247
256
  :headers
248
257
  )
249
258
 
@@ -254,6 +263,7 @@ module Temporalio
254
263
  :signal,
255
264
  :args,
256
265
  :cancellation,
266
+ :arg_hints,
257
267
  :headers
258
268
  )
259
269
 
@@ -270,6 +280,8 @@ module Temporalio
270
280
  :args,
271
281
  :id,
272
282
  :task_queue,
283
+ :static_summary,
284
+ :static_details,
273
285
  :cancellation,
274
286
  :cancellation_type,
275
287
  :parent_close_policy,
@@ -281,6 +293,9 @@ module Temporalio
281
293
  :cron_schedule,
282
294
  :memo,
283
295
  :search_attributes,
296
+ :priority,
297
+ :arg_hints,
298
+ :result_hint,
284
299
  :headers
285
300
  )
286
301
 
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ class Worker
5
+ # Base class for poller behaviors that control how polling scales.
6
+ class PollerBehavior
7
+ # @!visibility private
8
+ def _to_bridge_options
9
+ raise NotImplementedError, 'Subclasses must implement this method'
10
+ end
11
+
12
+ # A poller behavior that attempts to poll as long as a slot is available, up to the
13
+ # provided maximum. Cannot be less than two for workflow tasks, or one for other tasks.
14
+ class SimpleMaximum < PollerBehavior
15
+ # @return [Integer] Maximum number of concurrent poll requests.
16
+ attr_reader :maximum
17
+
18
+ # @param maximum [Integer] Maximum number of concurrent poll requests.
19
+ def initialize(maximum)
20
+ super()
21
+ @maximum = maximum
22
+ end
23
+
24
+ # @!visibility private
25
+ def _to_bridge_options
26
+ Internal::Bridge::Worker::PollerBehaviorSimpleMaximum.new(simple_maximum: @maximum)
27
+ end
28
+ end
29
+
30
+ # A poller behavior that automatically scales the number of pollers based on feedback
31
+ # from the server. A slot must be available before beginning polling.
32
+ class Autoscaling < PollerBehavior
33
+ # @return [Integer] Minimum number of poll calls (assuming slots are available).
34
+ attr_reader :minimum
35
+ # @return [Integer] Maximum number of poll calls that will ever be open at once.
36
+ attr_reader :maximum
37
+ # @return [Integer] Number of polls attempted initially before scaling kicks in.
38
+ attr_reader :initial
39
+
40
+ # @param minimum [Integer] Minimum number of poll calls (assuming slots are available).
41
+ # @param maximum [Integer] Maximum number of poll calls that will ever be open at once.
42
+ # @param initial [Integer] Number of polls attempted initially before scaling kicks in.
43
+ def initialize(minimum: 1, maximum: 100, initial: 5)
44
+ super()
45
+ @minimum = minimum
46
+ @maximum = maximum
47
+ @initial = initial
48
+ end
49
+
50
+ # @!visibility private
51
+ def _to_bridge_options
52
+ Internal::Bridge::Worker::PollerBehaviorAutoscaling.new(
53
+ minimum: @minimum,
54
+ maximum: @maximum,
55
+ initial: @initial
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Much of this logic taken from
4
- # https://github.com/ruby-concurrency/concurrent-ruby/blob/044020f44b36930b863b930f3ee8fa1e9f750469/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb,
5
- # see MIT license at
6
- # https://github.com/ruby-concurrency/concurrent-ruby/blob/044020f44b36930b863b930f3ee8fa1e9f750469/LICENSE.txt
7
-
8
3
  module Temporalio
9
4
  class Worker
10
5
  # Implementation of a thread pool. This implementation is a stripped down form of Concurrent Ruby's
11
6
  # `CachedThreadPool`.
12
7
  class ThreadPool
8
+ # Much of this logic taken from
9
+ # https://github.com/ruby-concurrency/concurrent-ruby/blob/044020f44b36930b863b930f3ee8fa1e9f750469/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb,
10
+ # see MIT license at
11
+ # https://github.com/ruby-concurrency/concurrent-ruby/blob/044020f44b36930b863b930f3ee8fa1e9f750469/LICENSE.txt
12
+
13
13
  # @return [ThreadPool] Default/shared thread pool instance with unlimited max threads.
14
14
  def self.default
15
15
  @default ||= new
@@ -125,7 +125,7 @@ module Temporalio
125
125
 
126
126
  private
127
127
 
128
- def locked_assign_worker(&block)
128
+ def locked_assign_worker(&block) # rubocop:disable Naming/PredicateMethod
129
129
  # keep growing if the pool is not at the minimum yet
130
130
  worker, = @ready.pop || locked_add_busy_worker
131
131
  if worker
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'temporalio/internal/bridge/worker'
4
+
3
5
  module Temporalio
4
6
  class Worker
5
7
  # Worker tuner that allows for dynamic customization of some aspects of worker configuration.
@@ -18,6 +20,14 @@ module Temporalio
18
20
  def initialize(slots) # rubocop:disable Lint/MissingSuper
19
21
  @slots = slots
20
22
  end
23
+
24
+ # @!visibility private
25
+ def _to_bridge_options
26
+ Internal::Bridge::Worker::TunerSlotSupplierOptions.new(
27
+ fixed_size: slots,
28
+ resource_based: nil
29
+ )
30
+ end
21
31
  end
22
32
 
23
33
  # A slot supplier that will dynamically adjust the number of slots based on resource usage.
@@ -34,6 +44,25 @@ module Temporalio
34
44
  @tuner_options = tuner_options
35
45
  @slot_options = slot_options
36
46
  end
47
+
48
+ # @!visibility private
49
+ def _to_bridge_options
50
+ Internal::Bridge::Worker::TunerSlotSupplierOptions.new(
51
+ fixed_size: nil,
52
+ resource_based: Internal::Bridge::Worker::TunerResourceBasedSlotSupplierOptions.new(
53
+ target_mem_usage: tuner_options.target_memory_usage,
54
+ target_cpu_usage: tuner_options.target_cpu_usage,
55
+ min_slots: slot_options.min_slots,
56
+ max_slots: slot_options.max_slots,
57
+ ramp_throttle: slot_options.ramp_throttle
58
+ )
59
+ )
60
+ end
61
+ end
62
+
63
+ # @!visibility private
64
+ def _to_bridge_options
65
+ raise ArgumentError, 'Tuner slot suppliers must be instances of Fixed or ResourceBased'
37
66
  end
38
67
  end
39
68
 
@@ -146,6 +175,15 @@ module Temporalio
146
175
  @activity_slot_supplier = activity_slot_supplier
147
176
  @local_activity_slot_supplier = local_activity_slot_supplier
148
177
  end
178
+
179
+ # @!visibility private
180
+ def _to_bridge_options
181
+ Internal::Bridge::Worker::TunerOptions.new(
182
+ workflow_slot_supplier: workflow_slot_supplier._to_bridge_options,
183
+ activity_slot_supplier: activity_slot_supplier._to_bridge_options,
184
+ local_activity_slot_supplier: local_activity_slot_supplier._to_bridge_options
185
+ )
186
+ end
149
187
  end
150
188
  end
151
189
  end
@@ -36,7 +36,7 @@ module Temporalio
36
36
  end
37
37
 
38
38
  # @!visibility private
39
- def _validate_worker(worker, worker_state)
39
+ def _validate_worker(workflow_worker, worker_state)
40
40
  # Do nothing
41
41
  end
42
42
 
@@ -137,7 +137,7 @@ module Temporalio
137
137
 
138
138
  # If it's eviction only, just evict inline and do nothing else
139
139
  if cache_remove_job && activation.jobs.size == 1
140
- evict(worker_state, activation.run_id)
140
+ evict(worker_state, activation.run_id, cache_remove_job)
141
141
  worker_state.logger.debug('Sending empty workflow completion') if LOG_ACTIVATIONS
142
142
  yield Internal::Bridge::Api::WorkflowCompletion::WorkflowActivationCompletion.new(
143
143
  run_id: activation.run_id,
@@ -173,7 +173,7 @@ module Temporalio
173
173
  end
174
174
 
175
175
  # Go ahead and evict if there is an eviction job
176
- evict(worker_state, activation.run_id) if cache_remove_job
176
+ evict(worker_state, activation.run_id, cache_remove_job) if cache_remove_job
177
177
 
178
178
  # Complete the activation
179
179
  worker_state.logger.debug("Sending workflow completion: #{completion}") if LOG_ACTIVATIONS
@@ -186,8 +186,12 @@ module Temporalio
186
186
  raise 'Missing initialize job in initial activation' unless init_job
187
187
 
188
188
  # Obtain definition
189
- definition = worker_state.workflow_definitions[init_job.workflow_type] ||
190
- worker_state.workflow_definitions[nil]
189
+ definition = worker_state.workflow_definitions[init_job.workflow_type]
190
+ # If not present and not reserved, try dynamic
191
+ if !definition && !Internal::ProtoUtils.reserved_name?(init_job.workflow_type)
192
+ definition = worker_state.workflow_definitions[nil]
193
+ end
194
+
191
195
  unless definition
192
196
  raise Error::ApplicationError.new(
193
197
  "Workflow type #{init_job.workflow_type} is not registered on this worker, available workflows: " +
@@ -209,13 +213,15 @@ module Temporalio
209
213
  interceptors: worker_state.workflow_interceptors,
210
214
  disable_eager_activity_execution: worker_state.disable_eager_activity_execution,
211
215
  illegal_calls: worker_state.illegal_calls,
212
- workflow_failure_exception_types: worker_state.workflow_failure_exception_types
216
+ workflow_failure_exception_types: worker_state.workflow_failure_exception_types,
217
+ unsafe_workflow_io_enabled: worker_state.unsafe_workflow_io_enabled,
218
+ assert_valid_local_activity: worker_state.assert_valid_local_activity
213
219
  )
214
220
  )
215
221
  end
216
222
 
217
- def evict(worker_state, run_id)
218
- worker_state.evict_running_workflow(run_id)
223
+ def evict(worker_state, run_id, cache_remove_job)
224
+ worker_state.evict_running_workflow(run_id, cache_remove_job)
219
225
  @executor._remove_workflow(worker_state, run_id)
220
226
  end
221
227
  end
@@ -13,7 +13,7 @@ module Temporalio
13
13
  end
14
14
 
15
15
  # @!visibility private
16
- def _validate_worker(worker, worker_state)
16
+ def _validate_worker(workflow_worker, worker_state)
17
17
  raise NotImplementedError
18
18
  end
19
19
 
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/api'
4
+ require 'temporalio/converters'
5
+ require 'temporalio/internal/bridge'
6
+ require 'temporalio/internal/bridge/worker'
7
+ require 'temporalio/internal/worker/multi_runner'
8
+ require 'temporalio/internal/worker/workflow_worker'
9
+ require 'temporalio/worker/interceptor'
10
+ require 'temporalio/worker/poller_behavior'
11
+ require 'temporalio/worker/thread_pool'
12
+ require 'temporalio/worker/tuner'
13
+ require 'temporalio/worker/workflow_executor'
14
+ require 'temporalio/workflow'
15
+ require 'temporalio/workflow_history'
16
+
17
+ module Temporalio
18
+ class Worker
19
+ # Replayer to replay workflows from existing history.
20
+ class WorkflowReplayer
21
+ Options = Data.define(
22
+ :workflows,
23
+ :namespace,
24
+ :task_queue,
25
+ :data_converter,
26
+ :workflow_executor,
27
+ :interceptors,
28
+ :identity,
29
+ :logger,
30
+ :illegal_workflow_calls,
31
+ :workflow_failure_exception_types,
32
+ :workflow_payload_codec_thread_pool,
33
+ :unsafe_workflow_io_enabled,
34
+ :debug_mode,
35
+ :runtime
36
+ )
37
+
38
+ # Options as returned from {options} representing the options passed to the constructor.
39
+ class Options; end # rubocop:disable Lint/EmptyClass
40
+
41
+ # @return [Options] Options for this replayer which has the same attributes as {initialize}.
42
+ attr_reader :options
43
+
44
+ # Create a new replayer. This combines some options from both {Worker.initialize} and {Client.initialize}.
45
+ #
46
+ # @param workflows [Array<Class<Workflow::Definition>>] Workflows for this replayer.
47
+ # @param namespace [String] Namespace as set in the workflow info.
48
+ # @param task_queue [String] Task queue as set in the workflow info.
49
+ # @param data_converter [Converters::DataConverter] Data converter to use for all data conversions to/from
50
+ # payloads.
51
+ # @param workflow_executor [WorkflowExecutor] Workflow executor that workflow tasks run within. This must be a
52
+ # {WorkflowExecutor::ThreadPool} currently.
53
+ # @param interceptors [Array<Interceptor::Workflow>] Workflow interceptors.
54
+ # @param identity [String, nil] Override the identity for this replater.
55
+ # @param logger [Logger] Logger to use. Defaults to stdout with warn level. Callers setting this logger are
56
+ # responsible for closing it.
57
+ # @param illegal_workflow_calls [Hash<String, [:all, Array<Symbol>]>] Set of illegal workflow calls that are
58
+ # considered unsafe/non-deterministic and will raise if seen. The key of the hash is the fully qualified string
59
+ # class name (no leading `::`). The value is either `:all` which means any use of the class, or an array of
60
+ # symbols for methods on the class that cannot be used. The methods refer to either instance or class methods,
61
+ # there is no way to differentiate at this time.
62
+ # @param workflow_failure_exception_types [Array<Class<Exception>>] Workflow failure exception types. This is the
63
+ # set of exception types that, if a workflow-thrown exception extends, will cause the workflow/update to fail
64
+ # instead of suspending the workflow via task failure. These are applied in addition to the
65
+ # `workflow_failure_exception_type` on the workflow definition class itself. If {::Exception} is set, it
66
+ # effectively will fail a workflow/update in all user exception cases.
67
+ # @param workflow_payload_codec_thread_pool [ThreadPool, nil] Thread pool to run payload codec encode/decode
68
+ # within. This is required if a payload codec exists and the worker is not fiber based. Codecs can potentially
69
+ # block execution which is why they need to be run in the background.
70
+ # @param unsafe_workflow_io_enabled [Boolean] If false, the default, workflow code that invokes io_wait on the
71
+ # fiber scheduler will fail. Instead of setting this to true, users are encouraged to use
72
+ # {Workflow::Unsafe.io_enabled} with a block for narrower enabling of IO.
73
+ # @param debug_mode [Boolean] If true, deadlock detection is disabled. Deadlock detection will fail workflow tasks
74
+ # if they block the thread for too long. This defaults to true if the `TEMPORAL_DEBUG` environment variable is
75
+ # `true` or `1`.
76
+ # @param runtime [Runtime] Runtime for this replayer.
77
+ #
78
+ # @yield If a block is present, this is the equivalent of calling {with_replay_worker} with the block and
79
+ # discarding the result.
80
+ def initialize(
81
+ workflows:,
82
+ namespace: 'ReplayNamespace',
83
+ task_queue: 'ReplayTaskQueue',
84
+ data_converter: Converters::DataConverter.default,
85
+ workflow_executor: WorkflowExecutor::ThreadPool.default,
86
+ interceptors: [],
87
+ identity: nil,
88
+ logger: Logger.new($stdout, level: Logger::WARN),
89
+ illegal_workflow_calls: Worker.default_illegal_workflow_calls,
90
+ workflow_failure_exception_types: [],
91
+ workflow_payload_codec_thread_pool: nil,
92
+ unsafe_workflow_io_enabled: false,
93
+ debug_mode: %w[true 1].include?(ENV['TEMPORAL_DEBUG'].to_s.downcase),
94
+ runtime: Runtime.default,
95
+ &
96
+ )
97
+ @options = Options.new(
98
+ workflows:,
99
+ namespace:,
100
+ task_queue:,
101
+ data_converter:,
102
+ workflow_executor:,
103
+ interceptors:,
104
+ identity:,
105
+ logger:,
106
+ illegal_workflow_calls:,
107
+ workflow_failure_exception_types:,
108
+ workflow_payload_codec_thread_pool:,
109
+ unsafe_workflow_io_enabled:,
110
+ debug_mode:,
111
+ runtime:
112
+ ).freeze
113
+ # Preload definitions and other settings
114
+ @workflow_definitions = Internal::Worker::WorkflowWorker.workflow_definitions(
115
+ workflows, should_enforce_versioning_behavior: false
116
+ )
117
+ @nondeterminism_as_workflow_fail, @nondeterminism_as_workflow_fail_for_types =
118
+ Internal::Worker::WorkflowWorker.bridge_workflow_failure_exception_type_options(
119
+ workflow_failure_exception_types:, workflow_definitions: @workflow_definitions
120
+ )
121
+ # If there is a block, we'll go ahead and assume it's for with_replay_worker
122
+ with_replay_worker(&) if block_given? # steep:ignore
123
+ end
124
+
125
+ # Replay a workflow history.
126
+ #
127
+ # If doing multiple histories, it is better to use {replay_workflows} or {with_replay_worker} since they create
128
+ # a replay worker just once instead of each time like this call does.
129
+ #
130
+ # @param history [WorkflowHistory] History to replay.
131
+ # @param raise_on_replay_failure [Boolean] If true, the default, this will raise an exception on any replay
132
+ # failure. If false and the replay fails, the failure will be available in {ReplayResult.replay_failure}.
133
+ #
134
+ # @return [ReplayResult] Result of the replay.
135
+ def replay_workflow(history, raise_on_replay_failure: true)
136
+ with_replay_worker { |worker| worker.replay_workflow(history, raise_on_replay_failure:) }
137
+ end
138
+
139
+ # Replay multiple workflow histories.
140
+ #
141
+ # @param histories [Enumerable<WorkflowHistory>] Histories to replay.
142
+ # @param raise_on_replay_failure [Boolean] If true, this will raise an exception on any replay failure. If false,
143
+ # the default, and the replay fails, the failure will be available in {ReplayResult.replay_failure}.
144
+ #
145
+ # @return [Array<ReplayResult>] Results of the replay.
146
+ def replay_workflows(histories, raise_on_replay_failure: false)
147
+ with_replay_worker do |worker|
148
+ histories.map { |h| worker.replay_workflow(h, raise_on_replay_failure:) }
149
+ end
150
+ end
151
+
152
+ # Run a block of code with a {ReplayWorker} to execute replays.
153
+ #
154
+ # @yield Block of code to run with a replay worker.
155
+ # @yieldparam [ReplayWorker] Worker to run replays on. Note, only one workflow can replay at a time.
156
+ # @yieldreturn [Object] Result of the block.
157
+ def with_replay_worker(&)
158
+ worker = ReplayWorker.new(
159
+ options:,
160
+ workflow_definitions: @workflow_definitions,
161
+ nondeterminism_as_workflow_fail: @nondeterminism_as_workflow_fail,
162
+ nondeterminism_as_workflow_fail_for_types: @nondeterminism_as_workflow_fail_for_types
163
+ )
164
+ begin
165
+ yield worker
166
+ ensure
167
+ worker._shutdown
168
+ end
169
+ end
170
+
171
+ # Result of a single workflow replay run.
172
+ class ReplayResult
173
+ # @return [WorkflowHistory] History originally passed in to the replayer.
174
+ attr_reader :history
175
+
176
+ # @return [Exception, nil] Failure during replay if any.
177
+ attr_reader :replay_failure
178
+
179
+ # @!visibility private
180
+ def initialize(history:, replay_failure:)
181
+ @history = history
182
+ @replay_failure = replay_failure
183
+ end
184
+ end
185
+
186
+ # Replay worker that can be used to replay individual workflow runs. Only one call to {replay_workflow} can be
187
+ # made at a time.
188
+ class ReplayWorker
189
+ # @!visibility private
190
+ def initialize(
191
+ options:,
192
+ workflow_definitions:,
193
+ nondeterminism_as_workflow_fail:,
194
+ nondeterminism_as_workflow_fail_for_types:
195
+ )
196
+ # Create the bridge worker and the replayer
197
+ @bridge_replayer, @bridge_worker = Internal::Bridge::Worker::WorkflowReplayer.new(
198
+ options.runtime._core_runtime,
199
+ Internal::Bridge::Worker::Options.new(
200
+ activity: false,
201
+ workflow: true,
202
+ namespace: options.namespace,
203
+ task_queue: options.task_queue,
204
+ tuner: Tuner.create_fixed(
205
+ workflow_slots: 2, activity_slots: 1, local_activity_slots: 1
206
+ )._to_bridge_options,
207
+ identity_override: options.identity,
208
+ max_cached_workflows: 2,
209
+ workflow_task_poller_behavior:
210
+ Temporalio::Worker::PollerBehavior::SimpleMaximum.new(2)._to_bridge_options,
211
+ nonsticky_to_sticky_poll_ratio: 1.0,
212
+ activity_task_poller_behavior:
213
+ Temporalio::Worker::PollerBehavior::SimpleMaximum.new(1)._to_bridge_options,
214
+ no_remote_activities: true,
215
+ sticky_queue_schedule_to_start_timeout: 1.0,
216
+ max_heartbeat_throttle_interval: 1.0,
217
+ default_heartbeat_throttle_interval: 1.0,
218
+ max_worker_activities_per_second: nil,
219
+ max_task_queue_activities_per_second: nil,
220
+ graceful_shutdown_period: 0.0,
221
+ nondeterminism_as_workflow_fail:,
222
+ nondeterminism_as_workflow_fail_for_types:,
223
+ deployment_options: Worker.default_deployment_options._to_bridge_options
224
+ )
225
+ )
226
+
227
+ # Create the workflow worker
228
+ @workflow_worker = Internal::Worker::WorkflowWorker.new(
229
+ bridge_worker: @bridge_worker,
230
+ namespace: options.namespace,
231
+ task_queue: options.task_queue,
232
+ workflow_definitions:,
233
+ workflow_executor: options.workflow_executor,
234
+ logger: options.logger,
235
+ data_converter: options.data_converter,
236
+ metric_meter: options.runtime.metric_meter,
237
+ workflow_interceptors: options.interceptors.select do |i|
238
+ i.is_a?(Interceptor::Workflow)
239
+ end,
240
+ disable_eager_activity_execution: false,
241
+ illegal_workflow_calls: options.illegal_workflow_calls,
242
+ workflow_failure_exception_types: options.workflow_failure_exception_types,
243
+ workflow_payload_codec_thread_pool: options.workflow_payload_codec_thread_pool,
244
+ unsafe_workflow_io_enabled: options.unsafe_workflow_io_enabled,
245
+ debug_mode: options.debug_mode,
246
+ on_eviction: proc { |_, remove_job| @last_workflow_remove_job = remove_job }, # steep:ignore
247
+ assert_valid_local_activity: ->(_) {}
248
+ )
249
+
250
+ # Create the runner
251
+ @runner = Internal::Worker::MultiRunner.new(workers: [self], shutdown_signals: [])
252
+ end
253
+
254
+ # Replay a workflow history.
255
+ #
256
+ # @param history [WorkflowHistory] History to replay.
257
+ # @param raise_on_replay_failure [Boolean] If true, the default, this will raise an exception on any replay
258
+ # failure. If false and the replay fails, the failure will be available in {ReplayResult.replay_failure}.
259
+ #
260
+ # @return [ReplayResult] Result of the replay.
261
+ def replay_workflow(history, raise_on_replay_failure: true)
262
+ raise ArgumentError, 'Expected history as WorkflowHistory' unless history.is_a?(WorkflowHistory)
263
+ # Due to our event processing model, only one can run at a time
264
+ raise 'Already running' if @running
265
+ raise 'Replayer shutdown' if @shutdown
266
+
267
+ # Push history proto
268
+ # TODO(cretz): Unset this
269
+ @running = true
270
+ @last_workflow_remove_job = nil
271
+ begin
272
+ @bridge_replayer.push_history(
273
+ history.workflow_id, Api::History::V1::History.new(events: history.events).to_proto
274
+ )
275
+
276
+ # Process events until workflow complete
277
+ until @last_workflow_remove_job
278
+ event = @runner.next_event
279
+ case event
280
+ when Internal::Worker::MultiRunner::Event::PollSuccess
281
+ @workflow_worker.handle_activation(
282
+ runner: @runner,
283
+ activation: Internal::Bridge::Api::WorkflowActivation::WorkflowActivation.decode(event.bytes),
284
+ decoded: false
285
+ )
286
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationDecoded
287
+ @workflow_worker.handle_activation(runner: @runner, activation: event.activation, decoded: true)
288
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationComplete
289
+ @workflow_worker.handle_activation_complete(
290
+ runner: @runner,
291
+ activation_completion: event.activation_completion,
292
+ encoded: event.encoded,
293
+ completion_complete_queue: event.completion_complete_queue
294
+ )
295
+ when Internal::Worker::MultiRunner::Event::WorkflowActivationCompletionComplete
296
+ # Ignore
297
+ else
298
+ raise "Unexpected event: #{event}"
299
+ end
300
+ end
301
+
302
+ # Create exception if removal is due to error
303
+ err = if @last_workflow_remove_job.reason == :NONDETERMINISM
304
+ Workflow::NondeterminismError.new(
305
+ "#{@last_workflow_remove_job.reason}: #{@last_workflow_remove_job.message}"
306
+ )
307
+ elsif !%i[CACHE_FULL LANG_REQUESTED].include?(@last_workflow_remove_job.reason)
308
+ Workflow::InvalidWorkflowStateError.new(
309
+ "#{@last_workflow_remove_job.reason}: #{@last_workflow_remove_job.message}"
310
+ )
311
+ end
312
+ # Raise if wanting to raise, otherwise return result
313
+ raise err if raise_on_replay_failure && err
314
+
315
+ ReplayResult.new(history:, replay_failure: err)
316
+ ensure
317
+ @running = false
318
+ end
319
+ end
320
+
321
+ # @!visibility private
322
+ def _shutdown
323
+ @shutdown = true
324
+ @runner.initiate_shutdown
325
+ # Wait for all-pollers-shutdown before finalizing
326
+ until @runner.next_event.is_a?(Internal::Worker::MultiRunner::Event::AllPollersShutDown); end
327
+ @runner.wait_complete_and_finalize_shutdown
328
+ @workflow_worker.on_shutdown_complete
329
+ @workflow_worker = nil
330
+ end
331
+
332
+ # @!visibility private
333
+ def _bridge_worker
334
+ @bridge_worker
335
+ end
336
+
337
+ # @!visibility private
338
+ def _initiate_shutdown
339
+ _bridge_worker.initiate_shutdown
340
+ end
341
+
342
+ # @!visibility private
343
+ def _wait_all_complete
344
+ # Do nothing
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end