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,395 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../lib/fractor"
|
4
|
+
|
5
|
+
module SpecializedWorkers
|
6
|
+
# First work type: Compute-intensive operations
|
7
|
+
class ComputeWork < Fractor::Work
|
8
|
+
def initialize(data, operation = :default, parameters = {})
|
9
|
+
super({
|
10
|
+
data: data,
|
11
|
+
operation: operation,
|
12
|
+
parameters: parameters
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
def data
|
17
|
+
input[:data]
|
18
|
+
end
|
19
|
+
|
20
|
+
def operation
|
21
|
+
input[:operation]
|
22
|
+
end
|
23
|
+
|
24
|
+
def parameters
|
25
|
+
input[:parameters]
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"ComputeWork: operation=#{operation}, parameters=#{parameters}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Second work type: Database operations
|
34
|
+
class DatabaseWork < Fractor::Work
|
35
|
+
def initialize(data = "", query_type = :select, table = "unknown", conditions = {})
|
36
|
+
super({
|
37
|
+
data: data,
|
38
|
+
query_type: query_type,
|
39
|
+
table: table,
|
40
|
+
conditions: conditions
|
41
|
+
})
|
42
|
+
end
|
43
|
+
|
44
|
+
def data
|
45
|
+
input[:data]
|
46
|
+
end
|
47
|
+
|
48
|
+
def query_type
|
49
|
+
input[:query_type]
|
50
|
+
end
|
51
|
+
|
52
|
+
def table
|
53
|
+
input[:table]
|
54
|
+
end
|
55
|
+
|
56
|
+
def conditions
|
57
|
+
input[:conditions]
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
"DatabaseWork: query_type=#{query_type}, table=#{table}, conditions=#{conditions}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# First worker type: Handles compute-intensive operations
|
66
|
+
class ComputeWorker < Fractor::Worker
|
67
|
+
def initialize
|
68
|
+
# Setup resources needed for computation
|
69
|
+
@compute_resources = { memory: 1024, cpu_cores: 4 }
|
70
|
+
end
|
71
|
+
|
72
|
+
def process(work)
|
73
|
+
# Only handle ComputeWork
|
74
|
+
unless work.is_a?(ComputeWork)
|
75
|
+
return Fractor::WorkResult.new(
|
76
|
+
error: "ComputeWorker can only process ComputeWork, got: #{work.class}",
|
77
|
+
work: work
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Process based on the requested operation
|
82
|
+
result = case work.operation
|
83
|
+
when :matrix_multiply then matrix_multiply(work.data, work.parameters)
|
84
|
+
when :image_transform then image_transform(work.data, work.parameters)
|
85
|
+
when :path_finding then path_finding(work.data, work.parameters)
|
86
|
+
else default_computation(work.data, work.parameters)
|
87
|
+
end
|
88
|
+
|
89
|
+
Fractor::WorkResult.new(
|
90
|
+
result: {
|
91
|
+
operation: work.operation,
|
92
|
+
computation_result: result,
|
93
|
+
resources_used: @compute_resources
|
94
|
+
},
|
95
|
+
work: work
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Computation methods
|
100
|
+
private
|
101
|
+
|
102
|
+
def matrix_multiply(_data, params)
|
103
|
+
# Simulate matrix multiplication
|
104
|
+
sleep(rand(0.05..0.2))
|
105
|
+
size = params[:size] || [3, 3]
|
106
|
+
"Matrix multiplication result: #{size[0]}x#{size[1]} matrix, determinant=#{rand(1..100)}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def image_transform(_data, params)
|
110
|
+
# Simulate image transformation
|
111
|
+
sleep(rand(0.1..0.3))
|
112
|
+
transforms = params[:transforms] || %i[rotate scale]
|
113
|
+
"Image transformation applied: #{transforms.join(", ")} with parameters #{params}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def path_finding(_data, params)
|
117
|
+
# Simulate path finding algorithm
|
118
|
+
sleep(rand(0.2..0.5))
|
119
|
+
algorithm = params[:algorithm] || :a_star
|
120
|
+
nodes = params[:nodes] || 10
|
121
|
+
"Path found using #{algorithm}: #{rand(1..nodes)} steps, cost=#{rand(10..100)}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def default_computation(data, _params)
|
125
|
+
# Default computation
|
126
|
+
sleep(rand(0.01..0.1))
|
127
|
+
"Default computation result for input: #{data}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Second worker type: Handles database operations
|
132
|
+
class DatabaseWorker < Fractor::Worker
|
133
|
+
def initialize
|
134
|
+
# Setup database connection and resources
|
135
|
+
@db_connection = { pool_size: 5, timeout: 30 }
|
136
|
+
end
|
137
|
+
|
138
|
+
def process(work)
|
139
|
+
# Only handle DatabaseWork
|
140
|
+
unless work.is_a?(DatabaseWork)
|
141
|
+
return Fractor::WorkResult.new(
|
142
|
+
error: "DatabaseWorker can only process DatabaseWork, got: #{work.class}",
|
143
|
+
work: work
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Process based on query type
|
148
|
+
result = case work.query_type
|
149
|
+
when :select then perform_select(work.table, work.conditions)
|
150
|
+
when :insert then perform_insert(work.table, work.data)
|
151
|
+
when :update then perform_update(work.table, work.data, work.conditions)
|
152
|
+
when :delete then perform_delete(work.table, work.conditions)
|
153
|
+
else default_query(work.query_type, work.table, work.conditions)
|
154
|
+
end
|
155
|
+
|
156
|
+
Fractor::WorkResult.new(
|
157
|
+
result: {
|
158
|
+
query_type: work.query_type,
|
159
|
+
table: work.table,
|
160
|
+
rows_affected: result[:rows_affected],
|
161
|
+
data: result[:data],
|
162
|
+
execution_time: result[:time]
|
163
|
+
},
|
164
|
+
work: work
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Database operation methods
|
169
|
+
private
|
170
|
+
|
171
|
+
def perform_select(_table, _conditions)
|
172
|
+
# Select query simulation
|
173
|
+
sleep(rand(0.01..0.1))
|
174
|
+
record_count = rand(0..20)
|
175
|
+
{
|
176
|
+
rows_affected: record_count,
|
177
|
+
data: record_count.times.map { |i| { id: i + 1, name: "Record #{i + 1}" } },
|
178
|
+
time: rand(0.01..0.05)
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def perform_insert(_table, _data)
|
183
|
+
# Insert query simulation
|
184
|
+
sleep(rand(0.01..0.05))
|
185
|
+
{
|
186
|
+
rows_affected: 1,
|
187
|
+
data: { id: rand(1000..9999) },
|
188
|
+
time: rand(0.01..0.03)
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
def perform_update(_table, _data, _conditions)
|
193
|
+
# Update query simulation
|
194
|
+
sleep(rand(0.01..0.1))
|
195
|
+
affected = rand(0..10)
|
196
|
+
{
|
197
|
+
rows_affected: affected,
|
198
|
+
data: nil,
|
199
|
+
time: rand(0.01..0.05)
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
def perform_delete(_table, _conditions)
|
204
|
+
# Delete query simulation
|
205
|
+
sleep(rand(0.01..0.05))
|
206
|
+
affected = rand(0..5)
|
207
|
+
{
|
208
|
+
rows_affected: affected,
|
209
|
+
data: nil,
|
210
|
+
time: rand(0.01..0.03)
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
def default_query(_type, _table, _conditions)
|
215
|
+
# Default query handling
|
216
|
+
sleep(rand(0.01..0.02))
|
217
|
+
{
|
218
|
+
rows_affected: 0,
|
219
|
+
data: nil,
|
220
|
+
time: rand(0.005..0.01)
|
221
|
+
}
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Controller class that manages both worker types
|
226
|
+
class HybridSystem
|
227
|
+
attr_reader :compute_results, :db_results
|
228
|
+
|
229
|
+
def initialize(compute_workers: 2, db_workers: 2)
|
230
|
+
# Create separate supervisors for each worker type
|
231
|
+
@compute_supervisor = Fractor::Supervisor.new(
|
232
|
+
worker_pools: [
|
233
|
+
{ worker_class: ComputeWorker, num_workers: compute_workers }
|
234
|
+
]
|
235
|
+
)
|
236
|
+
|
237
|
+
@db_supervisor = Fractor::Supervisor.new(
|
238
|
+
worker_pools: [
|
239
|
+
{ worker_class: DatabaseWorker, num_workers: db_workers }
|
240
|
+
]
|
241
|
+
)
|
242
|
+
|
243
|
+
@compute_results = []
|
244
|
+
@db_results = []
|
245
|
+
end
|
246
|
+
|
247
|
+
def process_mixed_workload(compute_tasks, db_tasks)
|
248
|
+
# Create and add compute work items
|
249
|
+
compute_work_items = compute_tasks.map do |task|
|
250
|
+
ComputeWork.new(task[:data], task[:operation], task[:parameters])
|
251
|
+
end
|
252
|
+
@compute_supervisor.add_work_items(compute_work_items)
|
253
|
+
|
254
|
+
# Create and add database work items
|
255
|
+
db_work_items = db_tasks.map do |task|
|
256
|
+
DatabaseWork.new(task[:data], task[:query_type], task[:table], task[:conditions])
|
257
|
+
end
|
258
|
+
@db_supervisor.add_work_items(db_work_items)
|
259
|
+
|
260
|
+
# Run the supervisors directly - this is more reliable
|
261
|
+
@compute_supervisor.run
|
262
|
+
@db_supervisor.run
|
263
|
+
|
264
|
+
# Get results directly from the supervisors
|
265
|
+
compute_results_agg = @compute_supervisor.results
|
266
|
+
db_results_agg = @db_supervisor.results
|
267
|
+
|
268
|
+
puts "Received compute results: #{compute_results_agg.results.size} items"
|
269
|
+
puts "Received database results: #{db_results_agg.results.size} items"
|
270
|
+
|
271
|
+
# Format and store results
|
272
|
+
@compute_results = format_compute_results(compute_results_agg)
|
273
|
+
@db_results = format_db_results(db_results_agg)
|
274
|
+
|
275
|
+
# Return combined results
|
276
|
+
{
|
277
|
+
computation: {
|
278
|
+
tasks: compute_tasks.size,
|
279
|
+
completed: @compute_results.size,
|
280
|
+
results: @compute_results
|
281
|
+
},
|
282
|
+
database: {
|
283
|
+
tasks: db_tasks.size,
|
284
|
+
completed: @db_results.size,
|
285
|
+
results: @db_results
|
286
|
+
}
|
287
|
+
}
|
288
|
+
end
|
289
|
+
|
290
|
+
private
|
291
|
+
|
292
|
+
def format_compute_results(results_aggregator)
|
293
|
+
# Format computation results
|
294
|
+
results_aggregator.results.map(&:result)
|
295
|
+
end
|
296
|
+
|
297
|
+
def format_db_results(results_aggregator)
|
298
|
+
# Format database results
|
299
|
+
results_aggregator.results.map(&:result)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Example usage
|
305
|
+
if __FILE__ == $PROGRAM_NAME
|
306
|
+
puts "Starting Specialized Workers Example"
|
307
|
+
puts "==================================="
|
308
|
+
puts "This example demonstrates two specialized worker types:"
|
309
|
+
puts "1. ComputeWorker: Handles compute-intensive operations"
|
310
|
+
puts "2. DatabaseWorker: Handles database operations"
|
311
|
+
puts "Each worker is designed to process a specific type of work."
|
312
|
+
puts
|
313
|
+
|
314
|
+
# Prepare compute tasks
|
315
|
+
compute_tasks = [
|
316
|
+
{
|
317
|
+
operation: :matrix_multiply,
|
318
|
+
data: "Matrix data...",
|
319
|
+
parameters: { size: [10, 10] }
|
320
|
+
},
|
321
|
+
{
|
322
|
+
operation: :image_transform,
|
323
|
+
data: "Image data...",
|
324
|
+
parameters: { transforms: %i[rotate scale blur], angle: 45, scale: 1.5 }
|
325
|
+
},
|
326
|
+
{
|
327
|
+
operation: :path_finding,
|
328
|
+
data: "Graph data...",
|
329
|
+
parameters: { algorithm: :dijkstra, nodes: 20, start: 1, end: 15 }
|
330
|
+
}
|
331
|
+
]
|
332
|
+
|
333
|
+
# Prepare database tasks
|
334
|
+
db_tasks = [
|
335
|
+
{
|
336
|
+
query_type: :select,
|
337
|
+
table: "users",
|
338
|
+
conditions: { active: true, role: "admin" }
|
339
|
+
},
|
340
|
+
{
|
341
|
+
query_type: :insert,
|
342
|
+
table: "orders",
|
343
|
+
data: "Order data..."
|
344
|
+
},
|
345
|
+
{
|
346
|
+
query_type: :update,
|
347
|
+
table: "products",
|
348
|
+
data: "Product data...",
|
349
|
+
conditions: { category: "electronics" }
|
350
|
+
},
|
351
|
+
{
|
352
|
+
query_type: :delete,
|
353
|
+
table: "sessions",
|
354
|
+
conditions: { expired: true }
|
355
|
+
}
|
356
|
+
]
|
357
|
+
|
358
|
+
compute_workers = 2
|
359
|
+
db_workers = 2
|
360
|
+
puts "Processing with #{compute_workers} compute workers and #{db_workers} database workers..."
|
361
|
+
puts
|
362
|
+
|
363
|
+
start_time = Time.now
|
364
|
+
system = SpecializedWorkers::HybridSystem.new(
|
365
|
+
compute_workers: compute_workers,
|
366
|
+
db_workers: db_workers
|
367
|
+
)
|
368
|
+
result = system.process_mixed_workload(compute_tasks, db_tasks)
|
369
|
+
end_time = Time.now
|
370
|
+
|
371
|
+
puts "Processing Results:"
|
372
|
+
puts "-----------------"
|
373
|
+
puts "Compute Tasks: #{result[:computation][:tasks]} submitted, #{result[:computation][:completed]} completed"
|
374
|
+
puts "Database Tasks: #{result[:database][:tasks]} submitted, #{result[:database][:completed]} completed"
|
375
|
+
puts
|
376
|
+
|
377
|
+
puts "Computation Results:"
|
378
|
+
result[:computation][:results].each_with_index do |compute_result, index|
|
379
|
+
puts "Task #{index + 1} (#{compute_result[:operation]}):"
|
380
|
+
puts " Result: #{compute_result[:computation_result]}"
|
381
|
+
puts " Resources: #{compute_result[:resources_used]}"
|
382
|
+
puts
|
383
|
+
end
|
384
|
+
|
385
|
+
puts "Database Results:"
|
386
|
+
result[:database][:results].each_with_index do |db_result, index|
|
387
|
+
puts "Query #{index + 1} (#{db_result[:query_type]} on #{db_result[:table]}):"
|
388
|
+
puts " Rows affected: #{db_result[:rows_affected]}"
|
389
|
+
puts " Execution time: #{db_result[:execution_time]} seconds"
|
390
|
+
puts " Data: #{db_result[:data].to_s[0..60]}..." unless db_result[:data].nil?
|
391
|
+
puts
|
392
|
+
end
|
393
|
+
|
394
|
+
puts "Processing completed in #{end_time - start_time} seconds"
|
395
|
+
end
|
@@ -8,16 +8,25 @@ module Fractor
|
|
8
8
|
def initialize
|
9
9
|
@results = []
|
10
10
|
@errors = []
|
11
|
+
@result_callbacks = []
|
11
12
|
end
|
12
13
|
|
13
14
|
def add_result(result)
|
14
15
|
if result.success?
|
15
|
-
puts "Work completed successfully: #{result}"
|
16
|
+
puts "Work completed successfully: Result: #{result.result}"
|
16
17
|
@results << result
|
17
18
|
else
|
18
19
|
puts "Error processing work: #{result}"
|
19
20
|
@errors << result
|
20
21
|
end
|
22
|
+
|
23
|
+
# Call any registered callbacks with the new result
|
24
|
+
@result_callbacks.each { |callback| callback.call(result) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Register a callback to be called when a new result is added
|
28
|
+
def on_new_result(&callback)
|
29
|
+
@result_callbacks << callback
|
21
30
|
end
|
22
31
|
|
23
32
|
def to_s
|