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
data/lib/fractor/supervisor.rb
CHANGED
@@ -1,56 +1,91 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'thread' # Required for Queue
|
4
|
-
|
5
3
|
module Fractor
|
6
4
|
# Supervises multiple WrappedRactors, distributes work, and aggregates results.
|
7
5
|
class Supervisor
|
8
|
-
attr_reader :work_queue, :workers, :results, :
|
6
|
+
attr_reader :work_queue, :workers, :results, :worker_pools
|
9
7
|
|
10
8
|
# Initializes the Supervisor.
|
11
|
-
# -
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
# - worker_pools: An array of worker pool configurations, each containing:
|
10
|
+
# - worker_class: The class inheriting from Fractor::Worker (e.g., MyWorker).
|
11
|
+
# - num_workers: The number of Ractors to spawn for this worker class.
|
12
|
+
# - continuous_mode: Whether to run in continuous mode without expecting a fixed work count.
|
13
|
+
def initialize(worker_pools: [], continuous_mode: false)
|
14
|
+
@worker_pools = worker_pools.map do |pool_config|
|
15
|
+
worker_class = pool_config[:worker_class]
|
16
|
+
num_workers = pool_config[:num_workers] || 2
|
17
|
+
|
18
|
+
raise ArgumentError, "#{worker_class} must inherit from Fractor::Worker" unless worker_class < Fractor::Worker
|
19
|
+
|
20
|
+
{
|
21
|
+
worker_class: worker_class,
|
22
|
+
num_workers: num_workers,
|
23
|
+
workers: [] # Will hold the WrappedRactor instances
|
24
|
+
}
|
20
25
|
end
|
21
26
|
|
22
|
-
@worker_class = worker_class
|
23
|
-
@work_class = work_class
|
24
27
|
@work_queue = Queue.new
|
25
28
|
@results = ResultAggregator.new
|
26
|
-
@
|
27
|
-
@workers = []
|
29
|
+
@workers = [] # Flattened array of all workers across all pools
|
28
30
|
@total_work_count = 0 # Track total items initially added
|
29
31
|
@ractors_map = {} # Map Ractor object to WrappedRactor instance
|
32
|
+
@continuous_mode = continuous_mode
|
33
|
+
@running = false
|
34
|
+
@work_callbacks = []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds a single work item to the queue.
|
38
|
+
# The item must be an instance of Fractor::Work or a subclass.
|
39
|
+
def add_work_item(work)
|
40
|
+
unless work.is_a?(Fractor::Work)
|
41
|
+
raise ArgumentError, "#{work.class} must be an instance of Fractor::Work"
|
42
|
+
end
|
43
|
+
|
44
|
+
@work_queue << work
|
45
|
+
@total_work_count += 1
|
46
|
+
return unless ENV["FRACTOR_DEBUG"]
|
47
|
+
|
48
|
+
puts "Work item added. Initial work count: #{@total_work_count}, Queue size: #{@work_queue.size}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Alias for better naming
|
52
|
+
alias add_work_item add_work_item
|
53
|
+
|
54
|
+
# Adds multiple work items to the queue.
|
55
|
+
# Each item must be an instance of Fractor::Work or a subclass.
|
56
|
+
def add_work_items(works)
|
57
|
+
works.each do |work|
|
58
|
+
add_work_item(work)
|
59
|
+
end
|
30
60
|
end
|
31
61
|
|
32
|
-
#
|
33
|
-
#
|
34
|
-
def
|
35
|
-
|
36
|
-
@total_work_count += items.size
|
37
|
-
puts "Work added. Initial work count: #{@total_work_count}, Queue size: #{@work_queue.size}"
|
62
|
+
# Register a callback to provide new work items
|
63
|
+
# The callback should return nil or empty array when no new work is available
|
64
|
+
def register_work_source(&callback)
|
65
|
+
@work_callbacks << callback
|
38
66
|
end
|
39
67
|
|
40
|
-
# Starts the worker Ractors.
|
68
|
+
# Starts the worker Ractors for all worker pools.
|
41
69
|
def start_workers
|
42
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
70
|
+
@worker_pools.each do |pool|
|
71
|
+
worker_class = pool[:worker_class]
|
72
|
+
num_workers = pool[:num_workers]
|
73
|
+
|
74
|
+
pool[:workers] = (1..num_workers).map do |i|
|
75
|
+
wrapped_ractor = WrappedRactor.new("worker #{worker_class}:#{i}", worker_class)
|
76
|
+
wrapped_ractor.start # Start the underlying Ractor
|
77
|
+
# Map the actual Ractor object to the WrappedRactor instance
|
78
|
+
@ractors_map[wrapped_ractor.ractor] = wrapped_ractor if wrapped_ractor.ractor
|
79
|
+
wrapped_ractor
|
80
|
+
end.compact
|
49
81
|
end
|
50
|
-
|
51
|
-
|
82
|
+
|
83
|
+
# Flatten all workers for easier access
|
84
|
+
@workers = @worker_pools.flat_map { |pool| pool[:workers] }
|
52
85
|
@ractors_map.compact! # Ensure map doesn't contain nil keys/values
|
53
|
-
|
86
|
+
return unless ENV["FRACTOR_DEBUG"]
|
87
|
+
|
88
|
+
puts "Workers started: #{@workers.size} active across #{@worker_pools.size} pools."
|
54
89
|
end
|
55
90
|
|
56
91
|
# Sets up a signal handler for graceful shutdown (Ctrl+C).
|
@@ -61,12 +96,10 @@ module Fractor
|
|
61
96
|
puts "\nCtrl+C received. Initiating immediate shutdown..."
|
62
97
|
puts "Attempting to close worker Ractors..."
|
63
98
|
workers_ref.each do |w|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
puts "Error closing Ractor #{w.name}: #{e.message}"
|
69
|
-
end
|
99
|
+
w.close # Use the close method of WrappedRactor
|
100
|
+
puts "Closed Ractor: #{w.name}"
|
101
|
+
rescue StandardError => e
|
102
|
+
puts "Error closing Ractor #{w.name}: #{e.message}"
|
70
103
|
end
|
71
104
|
puts "Exiting now."
|
72
105
|
exit(1) # Exit immediately
|
@@ -78,24 +111,48 @@ module Fractor
|
|
78
111
|
setup_signal_handler
|
79
112
|
start_workers
|
80
113
|
|
114
|
+
@running = true
|
81
115
|
processed_count = 0
|
82
|
-
|
83
|
-
|
116
|
+
|
117
|
+
# Main loop: Process events until conditions are met for termination
|
118
|
+
while @running && (@continuous_mode || processed_count < @total_work_count)
|
84
119
|
processed_count = @results.results.size + @results.errors.size
|
85
|
-
|
120
|
+
|
121
|
+
if ENV["FRACTOR_DEBUG"]
|
122
|
+
if @continuous_mode
|
123
|
+
puts "Continuous mode: Waiting for Ractor results. Processed: #{processed_count}, Queue size: #{@work_queue.size}"
|
124
|
+
else
|
125
|
+
puts "Waiting for Ractor results. Processed: #{processed_count}/#{@total_work_count}, Queue size: #{@work_queue.size}"
|
126
|
+
end
|
127
|
+
end
|
86
128
|
|
87
129
|
# Get active Ractor objects from the map keys
|
88
|
-
# Use keys from ractors_map for the active ractors
|
89
130
|
active_ractors = @ractors_map.keys
|
90
131
|
|
132
|
+
# Check for new work from callbacks if in continuous mode and queue is empty
|
133
|
+
if @continuous_mode && @work_queue.empty? && !@work_callbacks.empty?
|
134
|
+
@work_callbacks.each do |callback|
|
135
|
+
new_work = callback.call
|
136
|
+
add_work_items(new_work) if new_work && !new_work.empty?
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
91
140
|
# Break if no active workers and queue is empty, but work remains (indicates potential issue)
|
92
|
-
if active_ractors.empty? && @work_queue.empty? && processed_count < @total_work_count
|
93
|
-
|
94
|
-
|
141
|
+
if active_ractors.empty? && @work_queue.empty? && !@continuous_mode && processed_count < @total_work_count
|
142
|
+
if ENV["FRACTOR_DEBUG"]
|
143
|
+
puts "Warning: No active workers and queue is empty, but not all work is processed. Exiting loop."
|
144
|
+
end
|
145
|
+
break
|
95
146
|
end
|
96
147
|
|
97
|
-
#
|
98
|
-
|
148
|
+
# In continuous mode, just wait if no active ractors but keep running
|
149
|
+
if active_ractors.empty?
|
150
|
+
break unless @continuous_mode
|
151
|
+
|
152
|
+
sleep(0.1) # Small delay to avoid CPU spinning
|
153
|
+
next
|
154
|
+
|
155
|
+
end
|
99
156
|
|
100
157
|
# Ractor.select blocks until a message is available from any active Ractor
|
101
158
|
ready_ractor_obj, message = Ractor.select(*active_ractors)
|
@@ -103,47 +160,75 @@ module Fractor
|
|
103
160
|
# Find the corresponding WrappedRactor instance
|
104
161
|
wrapped_ractor = @ractors_map[ready_ractor_obj]
|
105
162
|
unless wrapped_ractor
|
106
|
-
|
163
|
+
if ENV["FRACTOR_DEBUG"]
|
164
|
+
puts "Warning: Received message from unknown Ractor: #{ready_ractor_obj}. Ignoring."
|
165
|
+
end
|
107
166
|
next
|
108
167
|
end
|
109
168
|
|
110
|
-
|
169
|
+
if ENV["FRACTOR_DEBUG"]
|
170
|
+
puts "Selected Ractor: #{wrapped_ractor.name}, Message Type: #{message[:type]}"
|
171
|
+
end
|
111
172
|
|
112
173
|
# Process the received message
|
113
174
|
case message[:type]
|
114
175
|
when :initialize
|
115
|
-
|
176
|
+
if ENV["FRACTOR_DEBUG"]
|
177
|
+
puts "Ractor initialized: #{message[:processor]}"
|
178
|
+
end
|
116
179
|
# Send work immediately upon initialization if available
|
117
180
|
send_next_work_if_available(wrapped_ractor)
|
118
181
|
when :result
|
119
182
|
# The message[:result] should be a WorkResult object
|
120
183
|
work_result = message[:result]
|
121
|
-
|
184
|
+
if ENV["FRACTOR_DEBUG"]
|
185
|
+
puts "Completed work: #{work_result.inspect} in Ractor: #{message[:processor]}"
|
186
|
+
end
|
122
187
|
@results.add_result(work_result)
|
123
|
-
|
124
|
-
|
188
|
+
if ENV["FRACTOR_DEBUG"]
|
189
|
+
puts "Result processed. Total processed: #{@results.results.size + @results.errors.size}"
|
190
|
+
puts "Aggregated Results: #{@results.inspect}" unless @continuous_mode
|
191
|
+
end
|
125
192
|
# Send next piece of work
|
126
193
|
send_next_work_if_available(wrapped_ractor)
|
127
194
|
when :error
|
128
195
|
# The message[:result] should be a WorkResult object containing the error
|
129
196
|
error_result = message[:result]
|
130
|
-
|
197
|
+
if ENV["FRACTOR_DEBUG"]
|
198
|
+
puts "Error processing work #{error_result.work&.inspect} in Ractor: #{message[:processor]}: #{error_result.error}"
|
199
|
+
end
|
131
200
|
@results.add_result(error_result) # Add error to aggregator
|
132
|
-
|
133
|
-
|
201
|
+
if ENV["FRACTOR_DEBUG"]
|
202
|
+
puts "Error handled. Total processed: #{@results.results.size + @results.errors.size}"
|
203
|
+
puts "Aggregated Results (including errors): #{@results.inspect}" unless @continuous_mode
|
204
|
+
end
|
134
205
|
# Send next piece of work even after an error
|
135
206
|
send_next_work_if_available(wrapped_ractor)
|
136
207
|
else
|
137
|
-
|
208
|
+
if ENV["FRACTOR_DEBUG"]
|
209
|
+
puts "Unknown message type received: #{message[:type]} from #{wrapped_ractor.name}"
|
210
|
+
end
|
138
211
|
end
|
139
212
|
# Update processed count for the loop condition
|
140
213
|
processed_count = @results.results.size + @results.errors.size
|
141
214
|
end
|
142
215
|
|
143
|
-
|
216
|
+
if ENV["FRACTOR_DEBUG"]
|
217
|
+
puts "Main loop finished."
|
218
|
+
end
|
219
|
+
return if @continuous_mode
|
220
|
+
|
221
|
+
return unless ENV["FRACTOR_DEBUG"]
|
222
|
+
|
144
223
|
puts "Final Aggregated Results: #{@results.inspect}"
|
145
224
|
end
|
146
225
|
|
226
|
+
# Stop the supervisor (for continuous mode)
|
227
|
+
def stop
|
228
|
+
@running = false
|
229
|
+
puts "Stopping supervisor..."
|
230
|
+
end
|
231
|
+
|
147
232
|
private
|
148
233
|
|
149
234
|
# Helper method to send the next available work item to a specific Ractor.
|
@@ -151,21 +236,33 @@ module Fractor
|
|
151
236
|
# Ensure the wrapped_ractor instance is valid and its underlying ractor is not closed
|
152
237
|
if wrapped_ractor && !wrapped_ractor.closed?
|
153
238
|
if !@work_queue.empty?
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
239
|
+
work_item = @work_queue.pop # Now directly a Work object
|
240
|
+
|
241
|
+
if ENV["FRACTOR_DEBUG"]
|
242
|
+
puts "Sending next work #{work_item.inspect} to Ractor: #{wrapped_ractor.name}"
|
243
|
+
end
|
158
244
|
wrapped_ractor.send(work_item) # Send the Work object
|
159
|
-
|
245
|
+
if ENV["FRACTOR_DEBUG"]
|
246
|
+
puts "Work sent to #{wrapped_ractor.name}."
|
247
|
+
end
|
160
248
|
else
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
#
|
165
|
-
|
249
|
+
if ENV["FRACTOR_DEBUG"]
|
250
|
+
puts "Work queue empty. Not sending new work to Ractor #{wrapped_ractor.name}."
|
251
|
+
end
|
252
|
+
# In continuous mode, don't close workers as more work may come
|
253
|
+
unless @continuous_mode
|
254
|
+
# Consider closing the Ractor if the queue is empty and no more work is expected.
|
255
|
+
# wrapped_ractor.close
|
256
|
+
# @ractors_map.delete(wrapped_ractor.ractor)
|
257
|
+
# if ENV["FRACTOR_DEBUG"]
|
258
|
+
# puts "Closed idle Ractor: #{wrapped_ractor.name}"
|
259
|
+
# end
|
260
|
+
end
|
166
261
|
end
|
167
262
|
else
|
168
|
-
|
263
|
+
if ENV["FRACTOR_DEBUG"]
|
264
|
+
puts "Attempted to send work to an invalid or closed Ractor: #{wrapped_ractor&.name || "unknown"}."
|
265
|
+
end
|
169
266
|
# Remove from map if found but closed
|
170
267
|
@ractors_map.delete(wrapped_ractor.ractor) if wrapped_ractor && @ractors_map.key?(wrapped_ractor.ractor)
|
171
268
|
end
|
data/lib/fractor/version.rb
CHANGED
data/lib/fractor.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'thread' # Required for Queue
|
4
|
-
|
5
3
|
# Require all component files
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative
|
11
|
-
require_relative
|
12
|
-
require_relative
|
4
|
+
require_relative "fractor/version"
|
5
|
+
require_relative "fractor/worker"
|
6
|
+
require_relative "fractor/work"
|
7
|
+
require_relative "fractor/work_result"
|
8
|
+
require_relative "fractor/result_aggregator"
|
9
|
+
require_relative "fractor/wrapped_ractor"
|
10
|
+
require_relative "fractor/supervisor"
|
13
11
|
|
14
12
|
# Fractor: Function-driven Ractors framework
|
15
13
|
module Fractor
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fractor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ronald Tse
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-05-
|
11
|
+
date: 2025-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Fractor is a lightweight Ruby framework designed to simplify the process
|
14
14
|
of distributing computational work across multiple Ractors.
|
@@ -20,11 +20,23 @@ extra_rdoc_files: []
|
|
20
20
|
files:
|
21
21
|
- ".rspec"
|
22
22
|
- ".rubocop.yml"
|
23
|
+
- ".rubocop_todo.yml"
|
23
24
|
- CODE_OF_CONDUCT.md
|
24
25
|
- README.adoc
|
25
26
|
- Rakefile
|
26
|
-
- examples/hierarchical_hasher.
|
27
|
-
- examples/
|
27
|
+
- examples/hierarchical_hasher/README.adoc
|
28
|
+
- examples/hierarchical_hasher/hierarchical_hasher.rb
|
29
|
+
- examples/multi_work_type/README.adoc
|
30
|
+
- examples/multi_work_type/multi_work_type.rb
|
31
|
+
- examples/pipeline_processing/README.adoc
|
32
|
+
- examples/pipeline_processing/pipeline_processing.rb
|
33
|
+
- examples/producer_subscriber/README.adoc
|
34
|
+
- examples/producer_subscriber/producer_subscriber.rb
|
35
|
+
- examples/scatter_gather/README.adoc
|
36
|
+
- examples/scatter_gather/scatter_gather.rb
|
37
|
+
- examples/simple/sample.rb
|
38
|
+
- examples/specialized_workers/README.adoc
|
39
|
+
- examples/specialized_workers/specialized_workers.rb
|
28
40
|
- lib/fractor.rb
|
29
41
|
- lib/fractor/result_aggregator.rb
|
30
42
|
- lib/fractor/supervisor.rb
|
@@ -33,7 +45,6 @@ files:
|
|
33
45
|
- lib/fractor/work_result.rb
|
34
46
|
- lib/fractor/worker.rb
|
35
47
|
- lib/fractor/wrapped_ractor.rb
|
36
|
-
- sample.rb
|
37
48
|
- sig/fractor.rbs
|
38
49
|
- tests/sample.rb.bak
|
39
50
|
- tests/sample_working.rb.bak
|
@@ -1,158 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'digest'
|
4
|
-
require_relative '../lib/fractor'
|
5
|
-
|
6
|
-
module HierarchicalHasher
|
7
|
-
class ChunkWork < Fractor::Work
|
8
|
-
work_type :chunk_hash
|
9
|
-
attr_reader :start, :length, :data
|
10
|
-
|
11
|
-
def initialize(start, length, data)
|
12
|
-
super(nil, { start: start, length: length, data: data })
|
13
|
-
@start = start
|
14
|
-
@length = length
|
15
|
-
@data = data
|
16
|
-
@retry_count = 0
|
17
|
-
@max_retries = 3
|
18
|
-
end
|
19
|
-
|
20
|
-
def should_retry?
|
21
|
-
@retry_count < @max_retries
|
22
|
-
end
|
23
|
-
|
24
|
-
def failed
|
25
|
-
@retry_count += 1
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
class ChunkResult < Fractor::WorkResult
|
30
|
-
attr_reader :start, :length, :hash_result
|
31
|
-
|
32
|
-
def initialize(work, hash_result)
|
33
|
-
super(work, hash_result)
|
34
|
-
@start = work.start
|
35
|
-
@length = work.length
|
36
|
-
@hash_result = hash_result
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class HashWorker < Fractor::Worker
|
41
|
-
work_type_accepted :chunk_hash
|
42
|
-
|
43
|
-
def process_work(work)
|
44
|
-
# Simulate some processing time
|
45
|
-
sleep(rand(0.01..0.05))
|
46
|
-
|
47
|
-
# Calculate SHA-3 hash for the chunk
|
48
|
-
hash = Digest::SHA3.hexdigest(work.data)
|
49
|
-
|
50
|
-
# Return the result
|
51
|
-
ChunkResult.new(work, hash)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class HashResultAssembler < Fractor::ResultAssembler
|
56
|
-
def finalize
|
57
|
-
return nil if @results.empty?
|
58
|
-
|
59
|
-
# Sort results by start position
|
60
|
-
sorted_results = @results.sort_by { |result| result.start }
|
61
|
-
|
62
|
-
# Concatenate all hashes with newlines
|
63
|
-
combined_hash_string = sorted_results.map(&:hash_result).join("\n")
|
64
|
-
|
65
|
-
# Calculate final SHA-3 hash
|
66
|
-
Digest::SHA3.hexdigest(combined_hash_string)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
class HashSupervisor < Fractor::Supervisor
|
71
|
-
def initialize(file_path, chunk_size = 1024, worker_count = 4)
|
72
|
-
super()
|
73
|
-
@file_path = file_path
|
74
|
-
@chunk_size = chunk_size
|
75
|
-
@worker_count = worker_count
|
76
|
-
@assembler = HashResultAssembler.new
|
77
|
-
|
78
|
-
# Create a queue for chunk work
|
79
|
-
chunk_queue = Fractor::Queue.new(work_types: [:chunk_hash])
|
80
|
-
add_queue(chunk_queue)
|
81
|
-
|
82
|
-
# Create a worker pool
|
83
|
-
worker_pool = Fractor::Pool.new(size: @worker_count)
|
84
|
-
@worker_count.times do
|
85
|
-
worker_pool.add_worker(HashWorker.new)
|
86
|
-
end
|
87
|
-
add_pool(worker_pool)
|
88
|
-
|
89
|
-
# Load the file and create work chunks
|
90
|
-
load_file_chunks
|
91
|
-
end
|
92
|
-
|
93
|
-
def load_file_chunks
|
94
|
-
File.open(@file_path, 'rb') do |file|
|
95
|
-
start_pos = 0
|
96
|
-
|
97
|
-
while chunk = file.read(@chunk_size)
|
98
|
-
work = ChunkWork.new(start_pos, chunk.length, chunk)
|
99
|
-
@queues.first.push(work)
|
100
|
-
start_pos += chunk.length
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def process_results
|
106
|
-
until @result_queue.empty? && @queues.all?(&:empty?) && @idle_workers == @active_workers.size
|
107
|
-
result_data = next_result
|
108
|
-
next if result_data.nil?
|
109
|
-
|
110
|
-
type, *data = result_data
|
111
|
-
|
112
|
-
case type
|
113
|
-
when :result
|
114
|
-
result = data.first
|
115
|
-
@assembler.add_result(result)
|
116
|
-
when :error
|
117
|
-
work, error = data
|
118
|
-
@assembler.add_failed_work(work, error)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Small sleep to prevent CPU spinning
|
122
|
-
sleep 0.001
|
123
|
-
end
|
124
|
-
|
125
|
-
# Return the final hash
|
126
|
-
@assembler.finalize
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# Example usage
|
132
|
-
if __FILE__ == $PROGRAM_NAME
|
133
|
-
if ARGV.empty?
|
134
|
-
puts "Usage: ruby hierarchical_hasher.rb <file_path> [worker_count]"
|
135
|
-
exit 1
|
136
|
-
end
|
137
|
-
|
138
|
-
file_path = ARGV[0]
|
139
|
-
worker_count = (ARGV[1] || 4).to_i
|
140
|
-
|
141
|
-
unless File.exist?(file_path)
|
142
|
-
puts "Error: File '#{file_path}' not found"
|
143
|
-
exit 1
|
144
|
-
end
|
145
|
-
|
146
|
-
puts "Starting hierarchical hasher with #{worker_count} workers..."
|
147
|
-
puts "Processing file: #{file_path}"
|
148
|
-
|
149
|
-
start_time = Time.now
|
150
|
-
supervisor = HierarchicalHasher::HashSupervisor.new(file_path, 1024, worker_count)
|
151
|
-
final_hash = supervisor.start
|
152
|
-
end_time = Time.now
|
153
|
-
|
154
|
-
puts "Final SHA-3 hash: #{final_hash}"
|
155
|
-
puts "Processing completed in #{end_time - start_time} seconds"
|
156
|
-
|
157
|
-
supervisor.shutdown
|
158
|
-
end
|