fractor 0.1.0 → 0.1.1
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 +2 -0
- data/.rubocop_todo.yml +82 -0
- data/README.adoc +281 -41
- data/examples/hierarchical_hasher/README.adoc +75 -0
- data/examples/hierarchical_hasher/hierarchical_hasher.rb +150 -0
- data/examples/multi_work_type/README.adoc +45 -0
- data/examples/multi_work_type/multi_work_type.rb +319 -0
- data/examples/pipeline_processing/README.adoc +44 -0
- data/examples/pipeline_processing/pipeline_processing.rb +216 -0
- data/examples/producer_subscriber/README.adoc +92 -0
- data/examples/producer_subscriber/producer_subscriber.rb +256 -0
- data/examples/scatter_gather/README.adoc +43 -0
- data/examples/scatter_gather/scatter_gather.rb +327 -0
- data/examples/simple/sample.rb +101 -0
- data/examples/specialized_workers/README.adoc +45 -0
- data/examples/specialized_workers/specialized_workers.rb +395 -0
- data/lib/fractor/result_aggregator.rb +10 -1
- data/lib/fractor/supervisor.rb +167 -70
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor.rb +7 -9
- metadata +16 -5
- data/examples/hierarchical_hasher.rb +0 -158
- data/examples/producer_subscriber.rb +0 -300
- data/sample.rb +0 -64
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../lib/fractor"
|
4
|
+
require "digest/sha2" # Using SHA2 which is more Ractor-compatible
|
5
|
+
|
6
|
+
module HierarchicalHasher
|
7
|
+
# Define our Work class
|
8
|
+
class ChunkWork < Fractor::Work
|
9
|
+
def initialize(data, start = 0, length = nil)
|
10
|
+
super({
|
11
|
+
data: data,
|
12
|
+
start: start,
|
13
|
+
length: length || data.bytesize
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
def data
|
18
|
+
input[:data]
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
input[:start]
|
23
|
+
end
|
24
|
+
|
25
|
+
def length
|
26
|
+
input[:length]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"ChunkWork: start=#{start}, length=#{length}, data_size=#{data.bytesize}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Define our Worker class
|
35
|
+
class HashWorker < Fractor::Worker
|
36
|
+
def process(work)
|
37
|
+
# Simulate some processing time
|
38
|
+
sleep(rand(0.01..0.05))
|
39
|
+
|
40
|
+
# Calculate SHA-256 hash for the chunk (using SHA2 which is Ractor-compatible)
|
41
|
+
begin
|
42
|
+
hash = Digest::SHA256.hexdigest(work.data)
|
43
|
+
|
44
|
+
# Return successful result
|
45
|
+
Fractor::WorkResult.new(
|
46
|
+
result: {
|
47
|
+
start: work.start,
|
48
|
+
length: work.length,
|
49
|
+
hash: hash
|
50
|
+
},
|
51
|
+
work: work
|
52
|
+
)
|
53
|
+
rescue StandardError => e
|
54
|
+
# Return error result if something goes wrong
|
55
|
+
Fractor::WorkResult.new(
|
56
|
+
error: "Failed to hash chunk: #{e.message}",
|
57
|
+
work: work
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class FileHasher
|
64
|
+
attr_reader :file_path, :chunk_size, :final_hash
|
65
|
+
|
66
|
+
def initialize(file_path, chunk_size = 1024, worker_count = 4)
|
67
|
+
@file_path = file_path
|
68
|
+
@chunk_size = chunk_size
|
69
|
+
@worker_count = worker_count
|
70
|
+
@final_hash = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def hash_file
|
74
|
+
# Create the supervisor with our worker class in a worker pool
|
75
|
+
supervisor = Fractor::Supervisor.new(
|
76
|
+
worker_pools: [
|
77
|
+
{ worker_class: HashWorker, num_workers: @worker_count }
|
78
|
+
]
|
79
|
+
)
|
80
|
+
|
81
|
+
# Load the file and create work chunks
|
82
|
+
load_file_chunks(supervisor)
|
83
|
+
|
84
|
+
# Run the processing
|
85
|
+
supervisor.run
|
86
|
+
|
87
|
+
# Process the results to get the final hash
|
88
|
+
@final_hash = finalize_hash(supervisor.results)
|
89
|
+
|
90
|
+
@final_hash
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def load_file_chunks(supervisor)
|
96
|
+
work_items = []
|
97
|
+
|
98
|
+
File.open(@file_path, "rb") do |file|
|
99
|
+
start_pos = 0
|
100
|
+
|
101
|
+
while (chunk = file.read(@chunk_size))
|
102
|
+
work_items << ChunkWork.new(chunk, start_pos, chunk.length)
|
103
|
+
start_pos += chunk.length
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
supervisor.add_work_items(work_items)
|
108
|
+
end
|
109
|
+
|
110
|
+
def finalize_hash(results_aggregator)
|
111
|
+
return nil if results_aggregator.results.empty?
|
112
|
+
|
113
|
+
# Sort results by start position
|
114
|
+
sorted_results = results_aggregator.results.sort_by { |result| result.result[:start] }
|
115
|
+
|
116
|
+
# Concatenate all hashes with newlines
|
117
|
+
combined_hash_string = sorted_results.map { |result| result.result[:hash] }.join("\n")
|
118
|
+
|
119
|
+
# Calculate final SHA-256 hash (instead of SHA3)
|
120
|
+
Digest::SHA256.hexdigest(combined_hash_string)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Example usage
|
126
|
+
if __FILE__ == $PROGRAM_NAME
|
127
|
+
if ARGV.empty?
|
128
|
+
puts "Usage: ruby hierarchical_hasher.rb <file_path> [worker_count]"
|
129
|
+
exit 1
|
130
|
+
end
|
131
|
+
|
132
|
+
file_path = ARGV[0]
|
133
|
+
worker_count = (ARGV[1] || 4).to_i
|
134
|
+
|
135
|
+
unless File.exist?(file_path)
|
136
|
+
puts "Error: File '#{file_path}' not found"
|
137
|
+
exit 1
|
138
|
+
end
|
139
|
+
|
140
|
+
puts "Starting hierarchical hasher with #{worker_count} workers..."
|
141
|
+
puts "Processing file: #{file_path}"
|
142
|
+
|
143
|
+
start_time = Time.now
|
144
|
+
hasher = HierarchicalHasher::FileHasher.new(file_path, 1024, worker_count)
|
145
|
+
final_hash = hasher.hash_file
|
146
|
+
end_time = Time.now
|
147
|
+
|
148
|
+
puts "Final SHA-256 hash: #{final_hash}"
|
149
|
+
puts "Processing completed in #{end_time - start_time} seconds"
|
150
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
= Multi-Work Type Example
|
2
|
+
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
This example demonstrates how to handle multiple types of work items within a single Fractor supervisor. It shows how a single worker can process different work types intelligently, applying different strategies based on the work's type.
|
6
|
+
|
7
|
+
== Key Concepts
|
8
|
+
|
9
|
+
* *Multiple Work Types*: Supporting different work classes within the same system
|
10
|
+
* *Polymorphic Processing*: Workers that adapt their processing based on work type
|
11
|
+
* *Type Detection*: Identifying and handling different work types appropriately
|
12
|
+
* *Unified Workflow*: Managing diverse work through a common supervisor
|
13
|
+
|
14
|
+
== Example Explanation
|
15
|
+
|
16
|
+
This example implements a system that processes two distinct work types:
|
17
|
+
|
18
|
+
1. *TextWork*: Handles text in various formats (plain text, Markdown, HTML, JSON)
|
19
|
+
2. *ImageWork*: Processes image data with different dimensions and formats
|
20
|
+
|
21
|
+
A single worker type (`MultiFormatWorker`) is capable of handling both work types, adapting its processing strategies based on the work's class.
|
22
|
+
|
23
|
+
== Features Demonstrated
|
24
|
+
|
25
|
+
* Creating and using multiple work type classes
|
26
|
+
* Designing workers that can handle diverse work types
|
27
|
+
* Type-based processing logic
|
28
|
+
* Proper error handling across different work types
|
29
|
+
* Classification and reporting of heterogeneous results
|
30
|
+
|
31
|
+
== Running the Example
|
32
|
+
|
33
|
+
[source,sh]
|
34
|
+
----
|
35
|
+
ruby examples/multi_work_type/multi_work_type.rb
|
36
|
+
----
|
37
|
+
|
38
|
+
== Expected Output
|
39
|
+
|
40
|
+
The example will show:
|
41
|
+
* Processing of multiple work types
|
42
|
+
* Different processing strategies applied to each type
|
43
|
+
* Type-specific result formats
|
44
|
+
* Performance statistics for each work type
|
45
|
+
* Aggregated results organized by type
|
@@ -0,0 +1,319 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../lib/fractor"
|
4
|
+
|
5
|
+
module MultiWorkType
|
6
|
+
# First work type for text data
|
7
|
+
class TextWork < Fractor::Work
|
8
|
+
def initialize(data, format = :plain, options = {})
|
9
|
+
super({ data: data, format: format, options: options })
|
10
|
+
end
|
11
|
+
|
12
|
+
def data
|
13
|
+
input[:data]
|
14
|
+
end
|
15
|
+
|
16
|
+
def format
|
17
|
+
input[:format]
|
18
|
+
end
|
19
|
+
|
20
|
+
def options
|
21
|
+
input[:options]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"TextWork: format=#{format}, options=#{options}, data=#{data.to_s[0..30]}..."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Second work type for image data
|
30
|
+
class ImageWork < Fractor::Work
|
31
|
+
def initialize(data, dimensions = [0, 0], format = :png)
|
32
|
+
super({ data: data, dimensions: dimensions, format: format })
|
33
|
+
end
|
34
|
+
|
35
|
+
def data
|
36
|
+
input[:data]
|
37
|
+
end
|
38
|
+
|
39
|
+
def dimensions
|
40
|
+
input[:dimensions]
|
41
|
+
end
|
42
|
+
|
43
|
+
def format
|
44
|
+
input[:format]
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"ImageWork: dimensions=#{dimensions.join("x")}, format=#{format}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# A single worker that can process both work types
|
53
|
+
class MultiFormatWorker < Fractor::Worker
|
54
|
+
def process(work)
|
55
|
+
# Differentiate processing based on work class
|
56
|
+
if work.is_a?(TextWork)
|
57
|
+
process_text(work)
|
58
|
+
elsif work.is_a?(ImageWork)
|
59
|
+
process_image(work)
|
60
|
+
else
|
61
|
+
# Return error for unsupported work types
|
62
|
+
error = TypeError.new("Unsupported work type: #{work.class}")
|
63
|
+
Fractor::WorkResult.new(
|
64
|
+
error: error,
|
65
|
+
work: work
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def process_text(work)
|
73
|
+
# Process text based on format
|
74
|
+
sleep(rand(0.01..0.05)) # Simulate processing time
|
75
|
+
|
76
|
+
processed_text = case work.format
|
77
|
+
when :markdown then process_markdown(work.data, work.options)
|
78
|
+
when :html then process_html(work.data, work.options)
|
79
|
+
when :json then process_json(work.data, work.options)
|
80
|
+
else work.data.upcase # Simple transformation for plain text
|
81
|
+
end
|
82
|
+
|
83
|
+
Fractor::WorkResult.new(
|
84
|
+
result: {
|
85
|
+
work_type: :text,
|
86
|
+
original_format: work.format,
|
87
|
+
transformed_data: processed_text,
|
88
|
+
metadata: {
|
89
|
+
word_count: processed_text.split(/\s+/).size,
|
90
|
+
char_count: processed_text.length
|
91
|
+
}
|
92
|
+
},
|
93
|
+
work: work
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_image(work)
|
98
|
+
# Simulate image processing operations
|
99
|
+
sleep(rand(0.03..0.1)) # Simulate processing time
|
100
|
+
|
101
|
+
# Creating a safe copy of the data to avoid memory issues
|
102
|
+
# Avoid calling methods directly on the input that might cause memory issues
|
103
|
+
input_size = work.data.is_a?(String) ? work.data.size : 0
|
104
|
+
|
105
|
+
# In a real implementation, this would use image processing libraries
|
106
|
+
simulated_result = {
|
107
|
+
work_type: :image,
|
108
|
+
dimensions: work.dimensions,
|
109
|
+
format: work.format,
|
110
|
+
applied_filters: %i[sharpen contrast],
|
111
|
+
processing_metadata: {
|
112
|
+
original_size: input_size,
|
113
|
+
processed_size: (input_size * 0.8).to_i # Simulate compression
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
Fractor::WorkResult.new(
|
118
|
+
result: simulated_result,
|
119
|
+
work: work
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Format-specific processing methods
|
124
|
+
def process_markdown(text, _options)
|
125
|
+
# Simulate Markdown processing
|
126
|
+
headers = text.scan(/^#+\s+(.+)$/).flatten
|
127
|
+
links = text.scan(/\[(.+?)\]\((.+?)\)/)
|
128
|
+
|
129
|
+
"Processed Markdown: #{text.length} chars, #{headers.size} headers, #{links.size} links\n" \
|
130
|
+
"Headers: #{headers.join(", ")}\n" \
|
131
|
+
"#{text.gsub(/^#+\s+(.+)$/, '💫 \1 💫')}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def process_html(text, _options)
|
135
|
+
# Simulate HTML processing
|
136
|
+
tags = text.scan(/<(\w+)[^>]*>/).flatten
|
137
|
+
|
138
|
+
"Processed HTML: #{text.length} chars, #{tags.size} tags\n" \
|
139
|
+
"Tags: #{tags.uniq.join(", ")}\n" \
|
140
|
+
"#{text.gsub(%r{<(\w+)[^>]*>(.+?)</\1>}, '✨\2✨')}"
|
141
|
+
end
|
142
|
+
|
143
|
+
def process_json(text, _options)
|
144
|
+
# Simulate JSON processing
|
145
|
+
|
146
|
+
data = text.nil? ? {} : eval(text) # WARNING: Using eval for demonstration only
|
147
|
+
keys = data.keys
|
148
|
+
|
149
|
+
"Processed JSON: #{keys.size} top-level keys\n" \
|
150
|
+
"Keys: #{keys.join(", ")}\n" \
|
151
|
+
"Pretty-printed: #{data}"
|
152
|
+
rescue StandardError => e
|
153
|
+
"Invalid JSON: #{e.message}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Controller class for the example
|
158
|
+
class ContentProcessor
|
159
|
+
attr_reader :results
|
160
|
+
|
161
|
+
def initialize(worker_count = 4)
|
162
|
+
# Create supervisor with a MultiFormatWorker pool
|
163
|
+
@supervisor = Fractor::Supervisor.new(
|
164
|
+
worker_pools: [
|
165
|
+
{ worker_class: MultiFormatWorker, num_workers: worker_count }
|
166
|
+
]
|
167
|
+
)
|
168
|
+
|
169
|
+
@results = {
|
170
|
+
text: [],
|
171
|
+
image: [],
|
172
|
+
errors: []
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
def process_mixed_content(text_items, image_items)
|
177
|
+
# Create TextWork objects and add them to the supervisor
|
178
|
+
text_works = text_items.map do |item|
|
179
|
+
TextWork.new(item[:data], item[:format], item[:options] || {})
|
180
|
+
end
|
181
|
+
@supervisor.add_work_items(text_works)
|
182
|
+
|
183
|
+
# Create ImageWork objects and add them to the supervisor
|
184
|
+
image_works = image_items.map do |item|
|
185
|
+
ImageWork.new(item[:data], item[:dimensions], item[:format] || :png)
|
186
|
+
end
|
187
|
+
@supervisor.add_work_items(image_works)
|
188
|
+
|
189
|
+
# Process all work
|
190
|
+
@supervisor.run
|
191
|
+
|
192
|
+
# Separate results by work type
|
193
|
+
classify_results(@supervisor.results)
|
194
|
+
|
195
|
+
# Return the statistics
|
196
|
+
{
|
197
|
+
total_items: text_items.size + image_items.size,
|
198
|
+
processed: {
|
199
|
+
text: @results[:text].size,
|
200
|
+
image: @results[:image].size
|
201
|
+
},
|
202
|
+
errors: @results[:errors].size,
|
203
|
+
results: @results
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def classify_results(results_aggregator)
|
210
|
+
# Group results by work type
|
211
|
+
results_aggregator.results.each do |result|
|
212
|
+
if result.work.is_a?(TextWork)
|
213
|
+
@results[:text] << result.result
|
214
|
+
elsif result.work.is_a?(ImageWork)
|
215
|
+
@results[:image] << result.result
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Record errors
|
220
|
+
results_aggregator.errors.each do |error_result|
|
221
|
+
@results[:errors] << {
|
222
|
+
error: error_result.error,
|
223
|
+
work_type: error_result.work.class.name
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
puts "Processed #{@results[:text].size} text items and #{@results[:image].size} image items"
|
228
|
+
puts "Encountered #{@results[:errors].size} errors"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Example usage
|
234
|
+
if __FILE__ == $PROGRAM_NAME
|
235
|
+
puts "Starting Multi-Work Type Processing Example"
|
236
|
+
puts "=========================================="
|
237
|
+
puts "This example demonstrates processing different types of work items:"
|
238
|
+
puts "1. Text documents in various formats (plain, markdown, HTML, JSON)"
|
239
|
+
puts "2. Image data with different formats and dimensions"
|
240
|
+
puts "Both are processed by the same worker but with different strategies"
|
241
|
+
puts
|
242
|
+
|
243
|
+
# Sample text items
|
244
|
+
text_items = [
|
245
|
+
{
|
246
|
+
data: "This is a plain text document. It has no special formatting.",
|
247
|
+
format: :plain
|
248
|
+
},
|
249
|
+
{
|
250
|
+
data: "# Markdown Document\n\nThis is a **bold** statement. Here's a [link](https://example.com).",
|
251
|
+
format: :markdown
|
252
|
+
},
|
253
|
+
{
|
254
|
+
data: "<html><body><h1>HTML Document</h1><p>This is a paragraph.</p></body></html>",
|
255
|
+
format: :html
|
256
|
+
},
|
257
|
+
{
|
258
|
+
data: "{name: 'Product', price: 29.99, tags: ['electronics', 'gadget']}",
|
259
|
+
format: :json,
|
260
|
+
options: { pretty: true }
|
261
|
+
}
|
262
|
+
]
|
263
|
+
|
264
|
+
# Sample image items (simulated)
|
265
|
+
image_items = [
|
266
|
+
{
|
267
|
+
data: "simulated_jpeg_data_1",
|
268
|
+
dimensions: [800, 600],
|
269
|
+
format: :jpeg
|
270
|
+
},
|
271
|
+
{
|
272
|
+
data: "simulated_png_data_1",
|
273
|
+
dimensions: [1024, 768],
|
274
|
+
format: :png
|
275
|
+
},
|
276
|
+
{
|
277
|
+
data: "simulated_gif_data_1",
|
278
|
+
dimensions: [320, 240],
|
279
|
+
format: :gif
|
280
|
+
}
|
281
|
+
]
|
282
|
+
|
283
|
+
worker_count = 4
|
284
|
+
puts "Processing with #{worker_count} workers..."
|
285
|
+
puts
|
286
|
+
|
287
|
+
start_time = Time.now
|
288
|
+
processor = MultiWorkType::ContentProcessor.new(worker_count)
|
289
|
+
result = processor.process_mixed_content(text_items, image_items)
|
290
|
+
end_time = Time.now
|
291
|
+
|
292
|
+
puts "Processing Results:"
|
293
|
+
puts "-----------------"
|
294
|
+
puts "Total items: #{result[:total_items]}"
|
295
|
+
puts "Processed text items: #{result[:processed][:text]}"
|
296
|
+
puts "Processed image items: #{result[:processed][:image]}"
|
297
|
+
puts "Errors: #{result[:errors]}"
|
298
|
+
puts
|
299
|
+
|
300
|
+
puts "Text Processing Results:"
|
301
|
+
result[:results][:text].each_with_index do |text_result, index|
|
302
|
+
puts "Text Item #{index + 1} (#{text_result[:original_format]}):"
|
303
|
+
puts " #{text_result[:transformed_data].to_s.split("\n").first}"
|
304
|
+
puts " Word count: #{text_result[:metadata][:word_count]}"
|
305
|
+
puts " Character count: #{text_result[:metadata][:char_count]}"
|
306
|
+
puts
|
307
|
+
end
|
308
|
+
|
309
|
+
puts "Image Processing Results:"
|
310
|
+
result[:results][:image].each_with_index do |image_result, index|
|
311
|
+
puts "Image Item #{index + 1} (#{image_result[:format]}):"
|
312
|
+
puts " Dimensions: #{image_result[:dimensions].join("x")}"
|
313
|
+
puts " Applied filters: #{image_result[:applied_filters].join(", ")}"
|
314
|
+
puts " Compression: #{(1 - image_result[:processing_metadata][:processed_size].to_f / image_result[:processing_metadata][:original_size]).round(2) * 100}%"
|
315
|
+
puts
|
316
|
+
end
|
317
|
+
|
318
|
+
puts "Processing completed in #{end_time - start_time} seconds"
|
319
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
= Pipeline Processing Example
|
2
|
+
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
This example demonstrates the Pipeline Processing pattern implemented with Fractor. In this pattern, data flows through a series of sequential processing stages, where the output of one stage becomes the input to the next.
|
6
|
+
|
7
|
+
== Key Concepts
|
8
|
+
|
9
|
+
* *Pipeline*: A series of connected processing stages
|
10
|
+
* *Data Flow*: Information passes through each stage in sequence
|
11
|
+
* *Transformation*: Each stage performs a specific operation on the data
|
12
|
+
* *Concurrency*: Multiple items can be at different stages of the pipeline simultaneously
|
13
|
+
|
14
|
+
== Example Explanation
|
15
|
+
|
16
|
+
This example processes data through a multi-stage pipeline:
|
17
|
+
|
18
|
+
1. *Input Stage*: Raw data is prepared for processing
|
19
|
+
2. *Processing Stages*: Data moves through a series of transformations
|
20
|
+
3. *Output Stage*: Final results are collected and reported
|
21
|
+
|
22
|
+
Each stage of the pipeline can run concurrently on different workers, allowing for efficient parallel processing while maintaining the required order of operations.
|
23
|
+
|
24
|
+
== Features Demonstrated
|
25
|
+
|
26
|
+
* Sequential processing with dependencies between stages
|
27
|
+
* Concurrent execution of pipeline stages
|
28
|
+
* Processing optimizations through specialized workers
|
29
|
+
* Handling data flow between processing stages
|
30
|
+
|
31
|
+
== Running the Example
|
32
|
+
|
33
|
+
[source,sh]
|
34
|
+
----
|
35
|
+
ruby examples/pipeline_processing/pipeline_processing.rb
|
36
|
+
----
|
37
|
+
|
38
|
+
== Expected Output
|
39
|
+
|
40
|
+
The example will show:
|
41
|
+
* Data moving through each stage of the pipeline
|
42
|
+
* Workers processing different stages concurrently
|
43
|
+
* The transformation of data at each stage
|
44
|
+
* Final results after passing through the complete pipeline
|