temporalio 0.3.0-x86_64-linux → 0.4.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.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/Rakefile +1 -1
- data/lib/temporalio/activity/context.rb +13 -0
- data/lib/temporalio/activity/definition.rb +22 -5
- data/lib/temporalio/activity/info.rb +3 -0
- data/lib/temporalio/api/batch/v1/message.rb +6 -1
- data/lib/temporalio/api/command/v1/message.rb +1 -1
- data/lib/temporalio/api/common/v1/message.rb +2 -1
- data/lib/temporalio/api/deployment/v1/message.rb +38 -0
- data/lib/temporalio/api/enums/v1/batch_operation.rb +1 -1
- data/lib/temporalio/api/enums/v1/common.rb +1 -1
- data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
- data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
- data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
- data/lib/temporalio/api/enums/v1/reset.rb +1 -1
- data/lib/temporalio/api/enums/v1/workflow.rb +2 -1
- data/lib/temporalio/api/errordetails/v1/message.rb +3 -1
- data/lib/temporalio/api/failure/v1/message.rb +3 -1
- data/lib/temporalio/api/history/v1/message.rb +3 -1
- data/lib/temporalio/api/nexus/v1/message.rb +2 -1
- data/lib/temporalio/api/payload_visitor.rb +75 -7
- data/lib/temporalio/api/query/v1/message.rb +2 -1
- data/lib/temporalio/api/taskqueue/v1/message.rb +4 -1
- data/lib/temporalio/api/workflow/v1/message.rb +9 -1
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +40 -11
- data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/client/connection/workflow_service.rb +238 -28
- data/lib/temporalio/client/interceptor.rb +39 -0
- data/lib/temporalio/client/schedule.rb +25 -1
- data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
- data/lib/temporalio/client/workflow_execution.rb +19 -0
- data/lib/temporalio/client/workflow_handle.rb +3 -3
- data/lib/temporalio/client.rb +125 -2
- data/lib/temporalio/contrib/open_telemetry.rb +470 -0
- data/lib/temporalio/error.rb +1 -0
- data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
- data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
- data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +0 -0
- data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +1 -1
- data/lib/temporalio/internal/bridge/api/common/common.rb +2 -1
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +1 -1
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +1 -1
- data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +2 -1
- data/lib/temporalio/internal/bridge/runtime.rb +3 -0
- data/lib/temporalio/internal/bridge/testing.rb +3 -0
- data/lib/temporalio/internal/client/implementation.rb +232 -10
- data/lib/temporalio/internal/proto_utils.rb +34 -2
- data/lib/temporalio/internal/worker/activity_worker.rb +20 -8
- data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +57 -3
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +4 -2
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +11 -26
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
- data/lib/temporalio/internal/worker/workflow_instance.rb +76 -32
- data/lib/temporalio/internal/worker/workflow_worker.rb +62 -19
- data/lib/temporalio/runtime/metric_buffer.rb +94 -0
- data/lib/temporalio/runtime.rb +48 -10
- data/lib/temporalio/search_attributes.rb +13 -0
- data/lib/temporalio/testing/activity_environment.rb +42 -14
- data/lib/temporalio/testing/workflow_environment.rb +26 -3
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/worker/interceptor.rb +3 -0
- data/lib/temporalio/worker/thread_pool.rb +5 -5
- data/lib/temporalio/worker/tuner.rb +38 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +13 -8
- data/lib/temporalio/worker/workflow_executor.rb +1 -1
- data/lib/temporalio/worker/workflow_replayer.rb +350 -0
- data/lib/temporalio/worker.rb +58 -52
- data/lib/temporalio/workflow/definition.rb +40 -8
- data/lib/temporalio/workflow/future.rb +2 -2
- data/lib/temporalio/workflow/info.rb +22 -0
- data/lib/temporalio/workflow.rb +60 -8
- data/lib/temporalio/workflow_history.rb +26 -1
- data/temporalio.gemspec +2 -1
- metadata +25 -4
@@ -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
|
@@ -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(
|
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
|
-
|
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,14 @@ 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
|
213
218
|
)
|
214
219
|
)
|
215
220
|
end
|
216
221
|
|
217
|
-
def evict(worker_state, run_id)
|
218
|
-
worker_state.evict_running_workflow(run_id)
|
222
|
+
def evict(worker_state, run_id, cache_remove_job)
|
223
|
+
worker_state.evict_running_workflow(run_id, cache_remove_job)
|
219
224
|
@executor._remove_workflow(worker_state, run_id)
|
220
225
|
end
|
221
226
|
end
|
@@ -0,0 +1,350 @@
|
|
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/thread_pool'
|
11
|
+
require 'temporalio/worker/tuner'
|
12
|
+
require 'temporalio/worker/workflow_executor'
|
13
|
+
require 'temporalio/workflow'
|
14
|
+
require 'temporalio/workflow_history'
|
15
|
+
|
16
|
+
module Temporalio
|
17
|
+
class Worker
|
18
|
+
# Replayer to replay workflows from existing history.
|
19
|
+
class WorkflowReplayer
|
20
|
+
Options = Data.define(
|
21
|
+
:workflows,
|
22
|
+
:namespace,
|
23
|
+
:task_queue,
|
24
|
+
:data_converter,
|
25
|
+
:workflow_executor,
|
26
|
+
:interceptors,
|
27
|
+
:build_id,
|
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 build_id [String] Unique identifier for the current runtime. This is best set as a unique value
|
55
|
+
# representing all code and should change only when code does. This can be something like a git commit hash. If
|
56
|
+
# unset, default is hash of known Ruby code.
|
57
|
+
# @param identity [String, nil] Override the identity for this replater.
|
58
|
+
# @param logger [Logger] Logger to use. Defaults to stdout with warn level. Callers setting this logger are
|
59
|
+
# responsible for closing it.
|
60
|
+
# @param illegal_workflow_calls [Hash<String, [:all, Array<Symbol>]>] Set of illegal workflow calls that are
|
61
|
+
# considered unsafe/non-deterministic and will raise if seen. The key of the hash is the fully qualified string
|
62
|
+
# class name (no leading `::`). The value is either `:all` which means any use of the class, or an array of
|
63
|
+
# symbols for methods on the class that cannot be used. The methods refer to either instance or class methods,
|
64
|
+
# there is no way to differentiate at this time.
|
65
|
+
# @param workflow_failure_exception_types [Array<Class<Exception>>] Workflow failure exception types. This is the
|
66
|
+
# set of exception types that, if a workflow-thrown exception extends, will cause the workflow/update to fail
|
67
|
+
# instead of suspending the workflow via task failure. These are applied in addition to the
|
68
|
+
# `workflow_failure_exception_type` on the workflow definition class itself. If {::Exception} is set, it
|
69
|
+
# effectively will fail a workflow/update in all user exception cases.
|
70
|
+
# @param workflow_payload_codec_thread_pool [ThreadPool, nil] Thread pool to run payload codec encode/decode
|
71
|
+
# within. This is required if a payload codec exists and the worker is not fiber based. Codecs can potentially
|
72
|
+
# block execution which is why they need to be run in the background.
|
73
|
+
# @param unsafe_workflow_io_enabled [Boolean] If false, the default, workflow code that invokes io_wait on the
|
74
|
+
# fiber scheduler will fail. Instead of setting this to true, users are encouraged to use
|
75
|
+
# {Workflow::Unsafe.io_enabled} with a block for narrower enabling of IO.
|
76
|
+
# @param debug_mode [Boolean] If true, deadlock detection is disabled. Deadlock detection will fail workflow tasks
|
77
|
+
# if they block the thread for too long. This defaults to true if the `TEMPORAL_DEBUG` environment variable is
|
78
|
+
# `true` or `1`.
|
79
|
+
# @param runtime [Runtime] Runtime for this replayer.
|
80
|
+
#
|
81
|
+
# @yield If a block is present, this is the equivalent of calling {with_replay_worker} with the block and
|
82
|
+
# discarding the result.
|
83
|
+
def initialize(
|
84
|
+
workflows:,
|
85
|
+
namespace: 'ReplayNamespace',
|
86
|
+
task_queue: 'ReplayTaskQueue',
|
87
|
+
data_converter: Converters::DataConverter.default,
|
88
|
+
workflow_executor: WorkflowExecutor::ThreadPool.default,
|
89
|
+
interceptors: [],
|
90
|
+
build_id: Worker.default_build_id,
|
91
|
+
identity: nil,
|
92
|
+
logger: Logger.new($stdout, level: Logger::WARN),
|
93
|
+
illegal_workflow_calls: Worker.default_illegal_workflow_calls,
|
94
|
+
workflow_failure_exception_types: [],
|
95
|
+
workflow_payload_codec_thread_pool: nil,
|
96
|
+
unsafe_workflow_io_enabled: false,
|
97
|
+
debug_mode: %w[true 1].include?(ENV['TEMPORAL_DEBUG'].to_s.downcase),
|
98
|
+
runtime: Runtime.default,
|
99
|
+
&
|
100
|
+
)
|
101
|
+
@options = Options.new(
|
102
|
+
workflows:,
|
103
|
+
namespace:,
|
104
|
+
task_queue:,
|
105
|
+
data_converter:,
|
106
|
+
workflow_executor:,
|
107
|
+
interceptors:,
|
108
|
+
build_id:,
|
109
|
+
identity:,
|
110
|
+
logger:,
|
111
|
+
illegal_workflow_calls:,
|
112
|
+
workflow_failure_exception_types:,
|
113
|
+
workflow_payload_codec_thread_pool:,
|
114
|
+
unsafe_workflow_io_enabled:,
|
115
|
+
debug_mode:,
|
116
|
+
runtime:
|
117
|
+
).freeze
|
118
|
+
# Preload definitions and other settings
|
119
|
+
@workflow_definitions = Internal::Worker::WorkflowWorker.workflow_definitions(workflows)
|
120
|
+
@nondeterminism_as_workflow_fail, @nondeterminism_as_workflow_fail_for_types =
|
121
|
+
Internal::Worker::WorkflowWorker.bridge_workflow_failure_exception_type_options(
|
122
|
+
workflow_failure_exception_types:, workflow_definitions: @workflow_definitions
|
123
|
+
)
|
124
|
+
# If there is a block, we'll go ahead and assume it's for with_replay_worker
|
125
|
+
with_replay_worker(&) if block_given? # steep:ignore
|
126
|
+
end
|
127
|
+
|
128
|
+
# Replay a workflow history.
|
129
|
+
#
|
130
|
+
# If doing multiple histories, it is better to use {replay_workflows} or {with_replay_worker} since they create
|
131
|
+
# a replay worker just once instead of each time like this call does.
|
132
|
+
#
|
133
|
+
# @param history [WorkflowHistory] History to replay.
|
134
|
+
# @param raise_on_replay_failure [Boolean] If true, the default, this will raise an exception on any replay
|
135
|
+
# failure. If false and the replay fails, the failure will be available in {ReplayResult.replay_failure}.
|
136
|
+
#
|
137
|
+
# @return [ReplayResult] Result of the replay.
|
138
|
+
def replay_workflow(history, raise_on_replay_failure: true)
|
139
|
+
with_replay_worker { |worker| worker.replay_workflow(history, raise_on_replay_failure:) }
|
140
|
+
end
|
141
|
+
|
142
|
+
# Replay multiple workflow histories.
|
143
|
+
#
|
144
|
+
# @param histories [Enumerable<WorkflowHistory>] Histories to replay.
|
145
|
+
# @param raise_on_replay_failure [Boolean] If true, this will raise an exception on any replay failure. If false,
|
146
|
+
# the default, and the replay fails, the failure will be available in {ReplayResult.replay_failure}.
|
147
|
+
#
|
148
|
+
# @return [Array<ReplayResult>] Results of the replay.
|
149
|
+
def replay_workflows(histories, raise_on_replay_failure: false)
|
150
|
+
with_replay_worker do |worker|
|
151
|
+
histories.map { |h| worker.replay_workflow(h, raise_on_replay_failure:) }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Run a block of code with a {ReplayWorker} to execute replays.
|
156
|
+
#
|
157
|
+
# @yield Block of code to run with a replay worker.
|
158
|
+
# @yieldparam [ReplayWorker] Worker to run replays on. Note, only one workflow can replay at a time.
|
159
|
+
# @yieldreturn [Object] Result of the block.
|
160
|
+
def with_replay_worker(&)
|
161
|
+
worker = ReplayWorker.new(
|
162
|
+
options:,
|
163
|
+
workflow_definitions: @workflow_definitions,
|
164
|
+
nondeterminism_as_workflow_fail: @nondeterminism_as_workflow_fail,
|
165
|
+
nondeterminism_as_workflow_fail_for_types: @nondeterminism_as_workflow_fail_for_types
|
166
|
+
)
|
167
|
+
begin
|
168
|
+
yield worker
|
169
|
+
ensure
|
170
|
+
worker._shutdown
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Result of a single workflow replay run.
|
175
|
+
class ReplayResult
|
176
|
+
# @return [WorkflowHistory] History originally passed in to the replayer.
|
177
|
+
attr_reader :history
|
178
|
+
|
179
|
+
# @return [Exception, nil] Failure during replay if any.
|
180
|
+
attr_reader :replay_failure
|
181
|
+
|
182
|
+
# @!visibility private
|
183
|
+
def initialize(history:, replay_failure:)
|
184
|
+
@history = history
|
185
|
+
@replay_failure = replay_failure
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Replay worker that can be used to replay individual workflow runs. Only one call to {replay_workflow} can be
|
190
|
+
# made at a time.
|
191
|
+
class ReplayWorker
|
192
|
+
# @!visibility private
|
193
|
+
def initialize(
|
194
|
+
options:,
|
195
|
+
workflow_definitions:,
|
196
|
+
nondeterminism_as_workflow_fail:,
|
197
|
+
nondeterminism_as_workflow_fail_for_types:
|
198
|
+
)
|
199
|
+
# Create the bridge worker and the replayer
|
200
|
+
@bridge_replayer, @bridge_worker = Internal::Bridge::Worker::WorkflowReplayer.new(
|
201
|
+
options.runtime._core_runtime,
|
202
|
+
Internal::Bridge::Worker::Options.new(
|
203
|
+
activity: false,
|
204
|
+
workflow: true,
|
205
|
+
namespace: options.namespace,
|
206
|
+
task_queue: options.task_queue,
|
207
|
+
tuner: Tuner.create_fixed(
|
208
|
+
workflow_slots: 2, activity_slots: 1, local_activity_slots: 1
|
209
|
+
)._to_bridge_options,
|
210
|
+
build_id: options.build_id,
|
211
|
+
identity_override: options.identity,
|
212
|
+
max_cached_workflows: 2,
|
213
|
+
max_concurrent_workflow_task_polls: 1,
|
214
|
+
nonsticky_to_sticky_poll_ratio: 1.0,
|
215
|
+
max_concurrent_activity_task_polls: 1,
|
216
|
+
no_remote_activities: true,
|
217
|
+
sticky_queue_schedule_to_start_timeout: 1.0,
|
218
|
+
max_heartbeat_throttle_interval: 1.0,
|
219
|
+
default_heartbeat_throttle_interval: 1.0,
|
220
|
+
max_worker_activities_per_second: nil,
|
221
|
+
max_task_queue_activities_per_second: nil,
|
222
|
+
graceful_shutdown_period: 0.0,
|
223
|
+
use_worker_versioning: false,
|
224
|
+
nondeterminism_as_workflow_fail:,
|
225
|
+
nondeterminism_as_workflow_fail_for_types:
|
226
|
+
)
|
227
|
+
)
|
228
|
+
|
229
|
+
# Create the workflow worker
|
230
|
+
@workflow_worker = Internal::Worker::WorkflowWorker.new(
|
231
|
+
bridge_worker: @bridge_worker,
|
232
|
+
namespace: options.namespace,
|
233
|
+
task_queue: options.task_queue,
|
234
|
+
workflow_definitions:,
|
235
|
+
workflow_executor: options.workflow_executor,
|
236
|
+
logger: options.logger,
|
237
|
+
data_converter: options.data_converter,
|
238
|
+
metric_meter: options.runtime.metric_meter,
|
239
|
+
workflow_interceptors: options.interceptors.select do |i|
|
240
|
+
i.is_a?(Interceptor::Workflow)
|
241
|
+
end,
|
242
|
+
disable_eager_activity_execution: false,
|
243
|
+
illegal_workflow_calls: options.illegal_workflow_calls,
|
244
|
+
workflow_failure_exception_types: options.workflow_failure_exception_types,
|
245
|
+
workflow_payload_codec_thread_pool: options.workflow_payload_codec_thread_pool,
|
246
|
+
unsafe_workflow_io_enabled: options.unsafe_workflow_io_enabled,
|
247
|
+
debug_mode: options.debug_mode,
|
248
|
+
on_eviction: proc { |_, remove_job| @last_workflow_remove_job = remove_job } # steep:ignore
|
249
|
+
)
|
250
|
+
|
251
|
+
# Create the runner
|
252
|
+
@runner = Internal::Worker::MultiRunner.new(workers: [self], shutdown_signals: [])
|
253
|
+
end
|
254
|
+
|
255
|
+
# Replay a workflow history.
|
256
|
+
#
|
257
|
+
# @param history [WorkflowHistory] History to replay.
|
258
|
+
# @param raise_on_replay_failure [Boolean] If true, the default, this will raise an exception on any replay
|
259
|
+
# failure. If false and the replay fails, the failure will be available in {ReplayResult.replay_failure}.
|
260
|
+
#
|
261
|
+
# @return [ReplayResult] Result of the replay.
|
262
|
+
def replay_workflow(history, raise_on_replay_failure: true)
|
263
|
+
raise ArgumentError, 'Expected history as WorkflowHistory' unless history.is_a?(WorkflowHistory)
|
264
|
+
# Due to our event processing model, only one can run at a time
|
265
|
+
raise 'Already running' if @running
|
266
|
+
raise 'Replayer shutdown' if @shutdown
|
267
|
+
|
268
|
+
# Push history proto
|
269
|
+
# TODO(cretz): Unset this
|
270
|
+
@running = true
|
271
|
+
@last_workflow_remove_job = nil
|
272
|
+
begin
|
273
|
+
@bridge_replayer.push_history(
|
274
|
+
history.workflow_id, Api::History::V1::History.new(events: history.events).to_proto
|
275
|
+
)
|
276
|
+
|
277
|
+
# Process events until workflow complete
|
278
|
+
until @last_workflow_remove_job
|
279
|
+
event = @runner.next_event
|
280
|
+
case event
|
281
|
+
when Internal::Worker::MultiRunner::Event::PollSuccess
|
282
|
+
@workflow_worker.handle_activation(
|
283
|
+
runner: @runner,
|
284
|
+
activation: Internal::Bridge::Api::WorkflowActivation::WorkflowActivation.decode(event.bytes),
|
285
|
+
decoded: false
|
286
|
+
)
|
287
|
+
when Internal::Worker::MultiRunner::Event::WorkflowActivationDecoded
|
288
|
+
@workflow_worker.handle_activation(runner: @runner, activation: event.activation, decoded: true)
|
289
|
+
when Internal::Worker::MultiRunner::Event::WorkflowActivationComplete
|
290
|
+
@workflow_worker.handle_activation_complete(
|
291
|
+
runner: @runner,
|
292
|
+
activation_completion: event.activation_completion,
|
293
|
+
encoded: event.encoded,
|
294
|
+
completion_complete_queue: event.completion_complete_queue
|
295
|
+
)
|
296
|
+
when Internal::Worker::MultiRunner::Event::WorkflowActivationCompletionComplete
|
297
|
+
# Ignore
|
298
|
+
else
|
299
|
+
raise "Unexpected event: #{event}"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Create exception if removal is due to error
|
304
|
+
err = if @last_workflow_remove_job.reason == :NONDETERMINISM
|
305
|
+
Workflow::NondeterminismError.new(
|
306
|
+
"#{@last_workflow_remove_job.reason}: #{@last_workflow_remove_job.message}"
|
307
|
+
)
|
308
|
+
elsif !%i[CACHE_FULL LANG_REQUESTED].include?(@last_workflow_remove_job.reason)
|
309
|
+
Workflow::InvalidWorkflowStateError.new(
|
310
|
+
"#{@last_workflow_remove_job.reason}: #{@last_workflow_remove_job.message}"
|
311
|
+
)
|
312
|
+
end
|
313
|
+
# Raise if wanting to raise, otherwise return result
|
314
|
+
raise err if raise_on_replay_failure && err
|
315
|
+
|
316
|
+
ReplayResult.new(history:, replay_failure: err)
|
317
|
+
ensure
|
318
|
+
@running = false
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# @!visibility private
|
323
|
+
def _shutdown
|
324
|
+
@shutdown = true
|
325
|
+
@runner.initiate_shutdown
|
326
|
+
# Wait for all-pollers-shutdown before finalizing
|
327
|
+
until @runner.next_event.is_a?(Internal::Worker::MultiRunner::Event::AllPollersShutDown); end
|
328
|
+
@runner.wait_complete_and_finalize_shutdown
|
329
|
+
@workflow_worker.on_shutdown_complete
|
330
|
+
@workflow_worker = nil
|
331
|
+
end
|
332
|
+
|
333
|
+
# @!visibility private
|
334
|
+
def _bridge_worker
|
335
|
+
@bridge_worker
|
336
|
+
end
|
337
|
+
|
338
|
+
# @!visibility private
|
339
|
+
def _initiate_shutdown
|
340
|
+
_bridge_worker.initiate_shutdown
|
341
|
+
end
|
342
|
+
|
343
|
+
# @!visibility private
|
344
|
+
def _wait_all_complete
|
345
|
+
# Do nothing
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|