ruby_reactor 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -2
- data/README.md +177 -3
- data/Rakefile +25 -0
- data/documentation/data_pipelines.md +90 -84
- data/documentation/images/failed_order_processing.png +0 -0
- data/documentation/images/payment_workflow.png +0 -0
- data/documentation/interrupts.md +161 -0
- data/gui/.gitignore +24 -0
- data/gui/README.md +73 -0
- data/gui/eslint.config.js +23 -0
- data/gui/index.html +13 -0
- data/gui/package-lock.json +5925 -0
- data/gui/package.json +46 -0
- data/gui/postcss.config.js +6 -0
- data/gui/public/vite.svg +1 -0
- data/gui/src/App.css +42 -0
- data/gui/src/App.tsx +51 -0
- data/gui/src/assets/react.svg +1 -0
- data/gui/src/components/DagVisualizer.tsx +424 -0
- data/gui/src/components/Dashboard.tsx +163 -0
- data/gui/src/components/ErrorBoundary.tsx +47 -0
- data/gui/src/components/ReactorDetail.tsx +135 -0
- data/gui/src/components/StepInspector.tsx +492 -0
- data/gui/src/components/__tests__/DagVisualizer.test.tsx +140 -0
- data/gui/src/components/__tests__/ReactorDetail.test.tsx +111 -0
- data/gui/src/components/__tests__/StepInspector.test.tsx +408 -0
- data/gui/src/globals.d.ts +7 -0
- data/gui/src/index.css +14 -0
- data/gui/src/lib/utils.ts +13 -0
- data/gui/src/main.tsx +14 -0
- data/gui/src/test/setup.ts +11 -0
- data/gui/tailwind.config.js +11 -0
- data/gui/tsconfig.app.json +28 -0
- data/gui/tsconfig.json +7 -0
- data/gui/tsconfig.node.json +26 -0
- data/gui/vite.config.ts +8 -0
- data/gui/vitest.config.ts +13 -0
- data/lib/ruby_reactor/async_router.rb +12 -8
- data/lib/ruby_reactor/context.rb +35 -9
- data/lib/ruby_reactor/context_serializer.rb +15 -0
- data/lib/ruby_reactor/dependency_graph.rb +2 -0
- data/lib/ruby_reactor/dsl/compose_builder.rb +8 -0
- data/lib/ruby_reactor/dsl/interrupt_builder.rb +48 -0
- data/lib/ruby_reactor/dsl/interrupt_step_config.rb +21 -0
- data/lib/ruby_reactor/dsl/map_builder.rb +14 -2
- data/lib/ruby_reactor/dsl/reactor.rb +12 -0
- data/lib/ruby_reactor/dsl/step_builder.rb +4 -0
- data/lib/ruby_reactor/executor/compensation_manager.rb +60 -27
- data/lib/ruby_reactor/executor/graph_manager.rb +2 -0
- data/lib/ruby_reactor/executor/result_handler.rb +118 -39
- data/lib/ruby_reactor/executor/retry_manager.rb +12 -1
- data/lib/ruby_reactor/executor/step_executor.rb +38 -4
- data/lib/ruby_reactor/executor.rb +86 -13
- data/lib/ruby_reactor/interrupt_result.rb +20 -0
- data/lib/ruby_reactor/map/collector.rb +71 -35
- data/lib/ruby_reactor/map/dispatcher.rb +162 -0
- data/lib/ruby_reactor/map/element_executor.rb +62 -56
- data/lib/ruby_reactor/map/execution.rb +44 -4
- data/lib/ruby_reactor/map/helpers.rb +44 -6
- data/lib/ruby_reactor/map/result_enumerator.rb +105 -0
- data/lib/ruby_reactor/reactor.rb +187 -1
- data/lib/ruby_reactor/registry.rb +25 -0
- data/lib/ruby_reactor/sidekiq_workers/worker.rb +1 -1
- data/lib/ruby_reactor/step/compose_step.rb +22 -6
- data/lib/ruby_reactor/step/map_step.rb +78 -19
- data/lib/ruby_reactor/storage/adapter.rb +32 -0
- data/lib/ruby_reactor/storage/redis_adapter.rb +213 -11
- data/lib/ruby_reactor/template/dynamic_source.rb +32 -0
- data/lib/ruby_reactor/utils/code_extractor.rb +31 -0
- data/lib/ruby_reactor/version.rb +1 -1
- data/lib/ruby_reactor/web/api.rb +206 -0
- data/lib/ruby_reactor/web/application.rb +53 -0
- data/lib/ruby_reactor/web/config.ru +5 -0
- data/lib/ruby_reactor/web/public/assets/index-VdeLgH9k.js +19 -0
- data/lib/ruby_reactor/web/public/assets/index-_z-6BvuM.css +1 -0
- data/lib/ruby_reactor/web/public/index.html +14 -0
- data/lib/ruby_reactor/web/public/vite.svg +1 -0
- data/lib/ruby_reactor.rb +94 -28
- data/llms-full.txt +66 -0
- data/llms.txt +7 -0
- metadata +66 -2
data/lib/ruby_reactor/reactor.rb
CHANGED
|
@@ -6,6 +6,61 @@ module RubyReactor
|
|
|
6
6
|
|
|
7
7
|
attr_reader :context, :result, :undo_trace, :execution_trace
|
|
8
8
|
|
|
9
|
+
def self.find(id)
|
|
10
|
+
reactor_class_name = name
|
|
11
|
+
serialized_context = configuration.storage_adapter.retrieve_context(id, reactor_class_name)
|
|
12
|
+
raise Error::ValidationError, "Context '#{id}' not found" unless serialized_context
|
|
13
|
+
|
|
14
|
+
context = Context.deserialize_from_retry(serialized_context)
|
|
15
|
+
new(context)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.find_by_correlation_id(correlation_id)
|
|
19
|
+
reactor_class_name = name
|
|
20
|
+
context_id = configuration.storage_adapter.retrieve_context_id_by_correlation_id(
|
|
21
|
+
correlation_id,
|
|
22
|
+
reactor_class_name
|
|
23
|
+
)
|
|
24
|
+
raise Error::ValidationError, "Correlation ID '#{correlation_id}' not found" unless context_id
|
|
25
|
+
|
|
26
|
+
find(context_id)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.continue(id:, payload:, step_name:, idempotency_key: nil)
|
|
30
|
+
reactor = find(id)
|
|
31
|
+
result = reactor.continue(payload: payload, step_name: step_name, idempotency_key: idempotency_key)
|
|
32
|
+
|
|
33
|
+
if result.is_a?(RubyReactor::Failure) && result.respond_to?(:invalid_payload?) && result.invalid_payload?
|
|
34
|
+
# Raise exception to match expected behavior (strict mode for class method)
|
|
35
|
+
# We do NOT cancel the reactor, allowing the user to retry with valid payload
|
|
36
|
+
raise Error::InputValidationError, result.error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
result
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.continue_by_correlation_id(correlation_id:, payload:, step_name:, idempotency_key: nil)
|
|
43
|
+
reactor = find_by_correlation_id(correlation_id)
|
|
44
|
+
# We delegate to the class-level continue method to ensure auto-compensation logic applies
|
|
45
|
+
# by using the context ID found by find_by_correlation_id
|
|
46
|
+
continue(id: reactor.context.context_id, payload: payload, step_name: step_name, idempotency_key: idempotency_key)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.cancel(id:, reason:)
|
|
50
|
+
reactor = find(id)
|
|
51
|
+
reactor.cancel(reason)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.undo(id)
|
|
55
|
+
reactor = find(id)
|
|
56
|
+
reactor.undo
|
|
57
|
+
cancel(id: id, reason: "Undo triggered")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.configuration
|
|
61
|
+
RubyReactor::Configuration.instance
|
|
62
|
+
end
|
|
63
|
+
|
|
9
64
|
def initialize(context = {})
|
|
10
65
|
@context = context
|
|
11
66
|
@result = :unexecuted
|
|
@@ -21,11 +76,13 @@ module RubyReactor
|
|
|
21
76
|
configuration.async_router.perform_async(serialized_context)
|
|
22
77
|
else
|
|
23
78
|
# For sync reactors (potentially with async steps), execute normally
|
|
24
|
-
|
|
79
|
+
context = @context.is_a?(Context) ? @context : nil
|
|
80
|
+
executor = Executor.new(self.class, inputs, context)
|
|
25
81
|
@result = executor.execute
|
|
26
82
|
|
|
27
83
|
@context = executor.context
|
|
28
84
|
|
|
85
|
+
# Merge traces
|
|
29
86
|
@undo_trace = executor.undo_trace
|
|
30
87
|
@execution_trace = executor.execution_trace
|
|
31
88
|
|
|
@@ -36,6 +93,55 @@ module RubyReactor
|
|
|
36
93
|
end
|
|
37
94
|
end
|
|
38
95
|
|
|
96
|
+
def continue(payload:, step_name:, idempotency_key: nil)
|
|
97
|
+
_ = idempotency_key
|
|
98
|
+
|
|
99
|
+
unless @context.current_step
|
|
100
|
+
raise Error::ValidationError, "Cannot resume: context does not have a current step (was it interrupted?)"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if @context.cancelled
|
|
104
|
+
raise Error::ValidationError,
|
|
105
|
+
"Cannot resume: reactor has been cancelled (Reason: #{@context.cancellation_reason})"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
validate_continue_step!(step_name)
|
|
109
|
+
|
|
110
|
+
if (failure = validate_continue_payload(payload, step_name))
|
|
111
|
+
return failure
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
target_step = step_name
|
|
115
|
+
@context.set_result(target_step, payload)
|
|
116
|
+
|
|
117
|
+
# Resume execution
|
|
118
|
+
executor = Executor.new(self.class, {}, @context)
|
|
119
|
+
@result = executor.resume_execution
|
|
120
|
+
|
|
121
|
+
@context = executor.context
|
|
122
|
+
@undo_trace = executor.undo_trace
|
|
123
|
+
@execution_trace = executor.execution_trace
|
|
124
|
+
|
|
125
|
+
@result
|
|
126
|
+
rescue Error::InputValidationError => e
|
|
127
|
+
# This might catch other validations, but here we specifically want payload validation.
|
|
128
|
+
# The block above handles payload validation explicitly.
|
|
129
|
+
RubyReactor::Failure(e.message, invalid_payload: true)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def undo
|
|
133
|
+
executor = Executor.new(self.class, {}, @context)
|
|
134
|
+
executor.undo_all
|
|
135
|
+
executor.save_context
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def cancel(reason)
|
|
139
|
+
@context.cancelled = true
|
|
140
|
+
@context.cancellation_reason = reason
|
|
141
|
+
@context.status = "cancelled"
|
|
142
|
+
save_context
|
|
143
|
+
end
|
|
144
|
+
|
|
39
145
|
def validate!
|
|
40
146
|
# Validate reactor configuration
|
|
41
147
|
validate_steps!
|
|
@@ -71,5 +177,85 @@ module RubyReactor
|
|
|
71
177
|
|
|
72
178
|
raise Error::DependencyError, "Dependency graph contains cycles"
|
|
73
179
|
end
|
|
180
|
+
|
|
181
|
+
def validate_continue_step!(step_name)
|
|
182
|
+
return if step_name.to_s == @context.current_step.to_s
|
|
183
|
+
|
|
184
|
+
# Build graph to check if step is ready
|
|
185
|
+
graph_manager = Executor::GraphManager.new(self.class, DependencyGraph.new, @context)
|
|
186
|
+
graph_manager.build_and_validate!
|
|
187
|
+
graph_manager.mark_completed_steps_from_context
|
|
188
|
+
ready_steps = graph_manager.dependency_graph.ready_steps.map(&:name).map(&:to_s)
|
|
189
|
+
|
|
190
|
+
return if ready_steps.include?(step_name.to_s)
|
|
191
|
+
|
|
192
|
+
raise Error::ValidationError,
|
|
193
|
+
"Cannot resume: expected step '#{@context.current_step}' " \
|
|
194
|
+
"or ready steps #{ready_steps} but got '#{step_name}'"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def validate_continue_payload(payload, step_name)
|
|
198
|
+
step_config = self.class.steps[step_name.to_sym]
|
|
199
|
+
return unless step_config&.validation_schema
|
|
200
|
+
|
|
201
|
+
validation = step_config.validation_schema.call(payload)
|
|
202
|
+
|
|
203
|
+
return unless validation.failure?
|
|
204
|
+
|
|
205
|
+
# Track attempts
|
|
206
|
+
step_key = step_name.to_sym
|
|
207
|
+
@context.private_data[:interrupt_attempts] ||= {}
|
|
208
|
+
@context.private_data[:interrupt_attempts][step_key] ||= 0
|
|
209
|
+
@context.private_data[:interrupt_attempts][step_key] += 1
|
|
210
|
+
|
|
211
|
+
save_context # Persist the attempt count
|
|
212
|
+
|
|
213
|
+
current_attempts = @context.private_data[:interrupt_attempts][step_key]
|
|
214
|
+
max_attempts = step_config.max_attempts
|
|
215
|
+
|
|
216
|
+
if max_attempts != :infinity && current_attempts >= max_attempts
|
|
217
|
+
# Max attempts reached - Fail and Compensate
|
|
218
|
+
undo
|
|
219
|
+
|
|
220
|
+
# Instead of cancelling, we mark as failed so it shows up as failed in UI
|
|
221
|
+
@context.status = "failed"
|
|
222
|
+
@context.failure_reason = {
|
|
223
|
+
message: "Validation failed after #{max_attempts} attempts",
|
|
224
|
+
step_name: step_name,
|
|
225
|
+
errors: validation.errors.to_h,
|
|
226
|
+
payload: payload,
|
|
227
|
+
step_arguments: payload,
|
|
228
|
+
attempts: current_attempts,
|
|
229
|
+
validation_errors: validation.errors.to_h
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
save_context
|
|
233
|
+
|
|
234
|
+
return RubyReactor::Failure(
|
|
235
|
+
"Validation failed after #{max_attempts} attempts",
|
|
236
|
+
step_name: step_name,
|
|
237
|
+
step_arguments: payload,
|
|
238
|
+
validation_errors: validation.errors.to_h
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
failure = RubyReactor::Failure(validation.errors.to_h)
|
|
243
|
+
# We need a way to mark this failure as a validation failure
|
|
244
|
+
# For now, we rely on the error object inside Failure or just return Failure
|
|
245
|
+
# The PRD requires `result.invalid_payload?` to be true.
|
|
246
|
+
# Since we don't have that method on Failure yet, we might need to enhance Failure
|
|
247
|
+
# OR wrap it. For now, let's assume Failure wraps the error and we can check it.
|
|
248
|
+
# We'll use a specific error type to identify it.
|
|
249
|
+
failure.instance_variable_set(:@type, :input_validation)
|
|
250
|
+
def failure.invalid_payload? = true
|
|
251
|
+
failure
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def save_context
|
|
255
|
+
storage = configuration.storage_adapter
|
|
256
|
+
reactor_class_name = self.class.name || "AnonymousReactor-#{self.class.object_id}"
|
|
257
|
+
serialized_context = ContextSerializer.serialize(@context)
|
|
258
|
+
storage.store_context(@context.context_id, serialized_context, reactor_class_name)
|
|
259
|
+
end
|
|
74
260
|
end
|
|
75
261
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
|
|
5
|
+
module RubyReactor
|
|
6
|
+
# Registry for dynamically created reactor classes (e.g. from inline map/compose)
|
|
7
|
+
# This avoids polluting the global constant namespace with runtime-generated constants.
|
|
8
|
+
class Registry
|
|
9
|
+
@reactors = Concurrent::Map.new
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def register(name, klass)
|
|
13
|
+
@reactors[name] = klass
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def find(name)
|
|
17
|
+
@reactors[name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def clear!
|
|
21
|
+
@reactors.clear
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -37,8 +37,8 @@ module RubyReactor
|
|
|
37
37
|
|
|
38
38
|
# Resume execution from the failed step
|
|
39
39
|
executor = Executor.new(context.reactor_class, {}, context)
|
|
40
|
-
executor.compensation_manager.undo_stack.concat(context.undo_stack)
|
|
41
40
|
executor.resume_execution
|
|
41
|
+
executor.save_context
|
|
42
42
|
|
|
43
43
|
# Return the executor (which now has the result stored in it)
|
|
44
44
|
executor
|
|
@@ -29,12 +29,28 @@ module RubyReactor
|
|
|
29
29
|
handle_execution_result(result)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def self.compensate(_reason,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
def self.compensate(_reason, arguments, context)
|
|
33
|
+
step_name = context.current_step
|
|
34
|
+
composed_data = context.composed_contexts[step_name]
|
|
35
|
+
return RubyReactor.Success() unless composed_data && composed_data[:context]
|
|
36
|
+
|
|
37
|
+
child_context = composed_data[:context]
|
|
38
|
+
executor = RubyReactor::Executor.new(arguments[:composed_reactor_class], {}, child_context)
|
|
39
|
+
executor.undo_all
|
|
40
|
+
executor.save_context
|
|
41
|
+
|
|
42
|
+
RubyReactor.Success()
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.undo(_result, arguments, context)
|
|
46
|
+
step_name = context.current_step
|
|
47
|
+
composed_data = context.composed_contexts[step_name]
|
|
48
|
+
return RubyReactor.Success() unless composed_data && composed_data[:context]
|
|
49
|
+
|
|
50
|
+
child_context = composed_data[:context]
|
|
51
|
+
executor = RubyReactor::Executor.new(arguments[:composed_reactor_class], {}, child_context)
|
|
52
|
+
executor.undo_all
|
|
53
|
+
executor.save_context
|
|
38
54
|
|
|
39
55
|
RubyReactor.Success()
|
|
40
56
|
end
|
|
@@ -28,6 +28,9 @@ module RubyReactor
|
|
|
28
28
|
inputs = {}
|
|
29
29
|
|
|
30
30
|
mappings.each do |mapped_input_name, source|
|
|
31
|
+
# Handle serialized template objects (Hashes from Sidekiq)
|
|
32
|
+
source = ContextSerializer.deserialize_value(source) if source.is_a?(Hash) && source["_type"]
|
|
33
|
+
|
|
31
34
|
value = if source.is_a?(RubyReactor::Template::Element)
|
|
32
35
|
# Handle element reference
|
|
33
36
|
# For now assuming element() refers to the current map's element
|
|
@@ -88,6 +91,25 @@ module RubyReactor
|
|
|
88
91
|
|
|
89
92
|
link_contexts(child_context, context)
|
|
90
93
|
|
|
94
|
+
map_id = "#{context.context_id}:#{context.current_step}"
|
|
95
|
+
storage = RubyReactor.configuration.storage_adapter
|
|
96
|
+
storage.store_map_element_context_id(map_id, child_context.context_id, context.reactor_class.name)
|
|
97
|
+
|
|
98
|
+
# Set map metadata for failure handling
|
|
99
|
+
child_context.map_metadata = {
|
|
100
|
+
map_id: map_id,
|
|
101
|
+
parent_reactor_class_name: context.reactor_class.name,
|
|
102
|
+
index: nil # Inline map execution doesn't track index in metadata currently, but could
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Store reference in composed_contexts so the UI knows where to find elements
|
|
106
|
+
context.composed_contexts[context.current_step] = {
|
|
107
|
+
name: context.current_step,
|
|
108
|
+
type: :map_ref,
|
|
109
|
+
map_id: map_id,
|
|
110
|
+
element_reactor_class: arguments[:mapped_reactor_class].name
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
executor = RubyReactor::Executor.new(arguments[:mapped_reactor_class], {}, child_context)
|
|
92
114
|
executor.execute
|
|
93
115
|
executor.result
|
|
@@ -114,8 +136,11 @@ module RubyReactor
|
|
|
114
136
|
RubyReactor::Success(results)
|
|
115
137
|
else
|
|
116
138
|
# New behavior: extract successful values only
|
|
117
|
-
|
|
118
|
-
|
|
139
|
+
# New behavior: extract successful values only IF fail_fast is true behavior implies only values
|
|
140
|
+
# However, if fail_fast is false, we want to return results as is, or if logic dictates otherwise.
|
|
141
|
+
# wait, if fail_fast=false, we expect Result objects so we can check if success/failure.
|
|
142
|
+
# If we return only successes, we hide failures.
|
|
143
|
+
RubyReactor::Success(results)
|
|
119
144
|
end
|
|
120
145
|
end
|
|
121
146
|
|
|
@@ -134,24 +159,58 @@ module RubyReactor
|
|
|
134
159
|
def run_async(arguments, context, step_name)
|
|
135
160
|
map_id = "#{context.context_id}:#{step_name}"
|
|
136
161
|
context.map_operations[step_name.to_s] = map_id
|
|
137
|
-
prepare_async_execution(context, map_id, arguments[:source].
|
|
162
|
+
prepare_async_execution(context, map_id, arguments[:source].size)
|
|
138
163
|
|
|
139
164
|
reactor_class_info = build_reactor_class_info(arguments[:mapped_reactor_class], context, step_name)
|
|
140
165
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
initialize_map_metadata(map_id, arguments, context, reactor_class_info)
|
|
167
|
+
|
|
168
|
+
job_id = dispatch_async_map(map_id, arguments, context, reactor_class_info, step_name)
|
|
169
|
+
|
|
170
|
+
# Store reference in composed_contexts so the UI knows where to find elements
|
|
171
|
+
context.composed_contexts[step_name.to_s] = {
|
|
172
|
+
name: step_name.to_s,
|
|
173
|
+
type: :map_ref,
|
|
174
|
+
map_id: map_id,
|
|
175
|
+
element_reactor_class: arguments[:mapped_reactor_class].name
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
RubyReactor::AsyncResult.new(
|
|
179
|
+
job_id: job_id,
|
|
180
|
+
intermediate_results: context.intermediate_results,
|
|
181
|
+
execution_id: context.context_id
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def initialize_map_metadata(map_id, arguments, context, reactor_class_info)
|
|
186
|
+
storage = RubyReactor.configuration.storage_adapter
|
|
187
|
+
storage.initialize_map_operation(
|
|
188
|
+
map_id, arguments[:source].size, context.reactor_class.name,
|
|
189
|
+
strict_ordering: arguments[:strict_ordering], reactor_class_info: reactor_class_info
|
|
190
|
+
)
|
|
191
|
+
end
|
|
153
192
|
|
|
154
|
-
|
|
193
|
+
def dispatch_async_map(map_id, arguments, context, reactor_class_info, step_name)
|
|
194
|
+
if arguments[:batch_size]
|
|
195
|
+
# Use new Dispatcher with Backpressure
|
|
196
|
+
RubyReactor::Map::Dispatcher.perform(
|
|
197
|
+
map_id: map_id,
|
|
198
|
+
parent_context_id: context.context_id,
|
|
199
|
+
parent_reactor_class_name: context.reactor_class.name,
|
|
200
|
+
source: arguments[:source],
|
|
201
|
+
batch_size: arguments[:batch_size],
|
|
202
|
+
step_name: step_name,
|
|
203
|
+
argument_mappings: arguments[:argument_mappings],
|
|
204
|
+
strict_ordering: arguments[:strict_ordering],
|
|
205
|
+
mapped_reactor_class: arguments[:mapped_reactor_class],
|
|
206
|
+
fail_fast: arguments[:fail_fast]
|
|
207
|
+
)
|
|
208
|
+
queue_collector(map_id, context, step_name, arguments[:strict_ordering])
|
|
209
|
+
"map:#{map_id}"
|
|
210
|
+
else
|
|
211
|
+
queue_single_worker(map_id: map_id, arguments: arguments, context: context,
|
|
212
|
+
reactor_class_info: reactor_class_info, step_name: step_name)
|
|
213
|
+
end
|
|
155
214
|
end
|
|
156
215
|
|
|
157
216
|
def prepare_async_execution(context, map_id, count)
|
|
@@ -174,11 +233,11 @@ module RubyReactor
|
|
|
174
233
|
# rubocop:enable Metrics/ParameterLists
|
|
175
234
|
storage = RubyReactor.configuration.storage_adapter
|
|
176
235
|
storage.initialize_map_operation(
|
|
177
|
-
map_id, arguments[:source].
|
|
236
|
+
map_id, arguments[:source].size, context.reactor_class.name,
|
|
178
237
|
strict_ordering: arguments[:strict_ordering], reactor_class_info: reactor_class_info
|
|
179
238
|
)
|
|
180
239
|
|
|
181
|
-
limit ||= arguments[:source].
|
|
240
|
+
limit ||= arguments[:source].size
|
|
182
241
|
first_job_id = nil
|
|
183
242
|
arguments[:source].each_with_index do |element, index|
|
|
184
243
|
break if index >= limit
|
|
@@ -225,7 +284,7 @@ module RubyReactor
|
|
|
225
284
|
map_id: map_id, serialized_inputs: serialized_inputs,
|
|
226
285
|
reactor_class_info: reactor_class_info, strict_ordering: arguments[:strict_ordering],
|
|
227
286
|
parent_context_id: context.context_id, parent_reactor_class_name: context.reactor_class.name,
|
|
228
|
-
step_name: step_name.to_s
|
|
287
|
+
step_name: step_name.to_s, fail_fast: arguments[:fail_fast]
|
|
229
288
|
)
|
|
230
289
|
end
|
|
231
290
|
end
|
|
@@ -46,6 +46,38 @@ module RubyReactor
|
|
|
46
46
|
def expire(key, seconds)
|
|
47
47
|
raise NotImplementedError
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
def store_correlation_id(correlation_id, context_id, reactor_class_name)
|
|
51
|
+
raise NotImplementedError
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def retrieve_context_id_by_correlation_id(correlation_id, reactor_class_name)
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def delete_correlation_id(correlation_id, reactor_class_name)
|
|
59
|
+
raise NotImplementedError
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def delete_context(context_id, reactor_class_name)
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def scan_reactors(pattern: "*", count: 50)
|
|
67
|
+
raise NotImplementedError
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def find_context_by_id(context_id)
|
|
71
|
+
raise NotImplementedError
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def store_map_element_context_id(map_id, context_id, reactor_class_name)
|
|
75
|
+
raise NotImplementedError
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def retrieve_map_element_context_ids(map_id, reactor_class_name)
|
|
79
|
+
raise NotImplementedError
|
|
80
|
+
end
|
|
49
81
|
end
|
|
50
82
|
end
|
|
51
83
|
end
|