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.
@@ -1,300 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../lib/fractor'
4
-
5
- module ProducerSubscriber
6
- # Define work types
7
- class InitialWork < Fractor::Work
8
- work_type :initial_processing
9
- attr_reader :data, :depth
10
-
11
- def initialize(data, depth = 0)
12
- super(data: { data: data, depth: depth })
13
- @data = data
14
- @depth = depth
15
- @retry_count = 0
16
- @max_retries = 2
17
- end
18
-
19
- def should_retry?
20
- @retry_count < @max_retries
21
- end
22
-
23
- def failed
24
- @retry_count += 1
25
- end
26
- end
27
-
28
- class SubWork < Fractor::Work
29
- work_type :sub_processing
30
- attr_reader :data, :parent_id, :depth
31
-
32
- def initialize(data, parent_id, depth)
33
- super(data: { data: data, parent_id: parent_id, depth: depth })
34
- @data = data
35
- @parent_id = parent_id
36
- @depth = depth
37
- @retry_count = 0
38
- @max_retries = 2
39
- end
40
-
41
- def should_retry?
42
- @retry_count < @max_retries
43
- end
44
-
45
- def failed
46
- @retry_count += 1
47
- end
48
- end
49
-
50
- # Define work results
51
- class InitialWorkResult < Fractor::WorkResult
52
- attr_reader :processed_data, :sub_works
53
-
54
- def initialize(work, processed_data, sub_works = [])
55
- super(work, processed_data)
56
- @processed_data = processed_data
57
- @sub_works = sub_works
58
- end
59
- end
60
-
61
- class SubWorkResult < Fractor::WorkResult
62
- attr_reader :processed_data, :parent_id
63
-
64
- def initialize(work, processed_data)
65
- super(work, processed_data)
66
- @processed_data = processed_data
67
- @parent_id = work.parent_id
68
- end
69
- end
70
-
71
- # Define worker that can handle both types of work
72
- class MultiWorker < Fractor::Worker
73
- work_type_accepted [:initial_processing, :sub_processing]
74
-
75
- def process_work(work)
76
- case work.work_type
77
- when :initial_processing
78
- process_initial_work(work)
79
- when :sub_processing
80
- process_sub_work(work)
81
- end
82
- end
83
-
84
- private
85
-
86
- def process_initial_work(work)
87
- # Simulate processing time
88
- sleep(rand(0.01..0.05))
89
-
90
- # Process the data
91
- processed_data = "Processed: #{work.data}"
92
-
93
- # Create sub-works if we're not too deep
94
- sub_works = []
95
- if work.depth < 2 # Limit recursion depth
96
- # Split the work into smaller chunks
97
- 3.times do |i|
98
- sub_data = "#{work.data}-#{i}"
99
- sub_works << SubWork.new(sub_data, work.object_id, work.depth + 1)
100
- end
101
- end
102
-
103
- # Return result with sub-works
104
- InitialWorkResult.new(work, processed_data, sub_works)
105
- end
106
-
107
- def process_sub_work(work)
108
- # Simulate processing time
109
- sleep(rand(0.01..0.03))
110
-
111
- # Process the data
112
- processed_data = "Sub-processed: #{work.data} (depth: #{work.depth})"
113
-
114
- # Create more sub-works if we're not too deep
115
- sub_works = []
116
- if work.depth < 3 # Limit recursion depth
117
- # Create fewer sub-works as we go deeper
118
- (4 - work.depth).times do |i|
119
- sub_data = "#{work.data}-#{i}"
120
- sub_works << SubWork.new(sub_data, work.parent_id, work.depth + 1)
121
- end
122
- end
123
-
124
- # Return result with any new sub-works
125
- result = SubWorkResult.new(work, processed_data)
126
- [result, sub_works]
127
- end
128
- end
129
-
130
- # Define result assembler
131
- class TreeResultAssembler < Fractor::ResultAssembler
132
- def initialize
133
- super()
134
- @result_tree = {}
135
- @pending_work_count = 0
136
- end
137
-
138
- def track_new_work(count = 1)
139
- @pending_work_count += count
140
- end
141
-
142
- def work_completed
143
- @pending_work_count -= 1
144
- end
145
-
146
- def add_result(result)
147
- super(result)
148
- work_completed
149
-
150
- case result
151
- when InitialWorkResult
152
- @result_tree[result.work.object_id] = {
153
- data: result.processed_data,
154
- children: []
155
- }
156
- when SubWorkResult
157
- parent = @result_tree[result.parent_id]
158
- if parent
159
- parent[:children] << result.processed_data
160
- end
161
- end
162
- end
163
-
164
- def all_work_complete?
165
- @pending_work_count <= 0
166
- end
167
-
168
- def finalize
169
- # Build a formatted tree representation
170
- format_tree
171
- end
172
-
173
- private
174
-
175
- def format_tree
176
- result = []
177
- @result_tree.each do |id, node|
178
- result << "Root: #{node[:data]}"
179
- node[:children].each_with_index do |child, index|
180
- result << " ├─ Child #{index + 1}: #{child}"
181
- end
182
- result << ""
183
- end
184
- result.join("\n")
185
- end
186
- end
187
-
188
- # Define supervisor
189
- class TreeSupervisor < Fractor::Supervisor
190
- def initialize(initial_data, worker_count = 4)
191
- super()
192
- @initial_data = initial_data
193
- @worker_count = worker_count
194
- @assembler = TreeResultAssembler.new
195
-
196
- # Create a queue that can handle both work types
197
- work_queue = Fractor::Queue.new(work_types: [:initial_processing, :sub_processing])
198
- add_queue(work_queue)
199
-
200
- # Create a worker pool
201
- worker_pool = Fractor::Pool.new(size: @worker_count)
202
- @worker_count.times do
203
- worker_pool.add_worker(MultiWorker.new)
204
- end
205
- add_pool(worker_pool)
206
-
207
- # Create initial work
208
- initial_data.each do |data|
209
- work = InitialWork.new(data)
210
- @queues.first.push(work)
211
- @assembler.track_new_work
212
- end
213
- end
214
-
215
- def process_results
216
- until @assembler.all_work_complete? && @queues.all?(&:empty?)
217
- result_data = next_result
218
- next if result_data.nil?
219
-
220
- type, *data = result_data
221
-
222
- case type
223
- when :result
224
- result = data.first
225
-
226
- if result.is_a?(Array)
227
- # Handle the case where we get a result and sub-works
228
- sub_result, sub_works = result
229
- @assembler.add_result(sub_result)
230
-
231
- # Add new sub-works to the queue
232
- if sub_works && !sub_works.empty?
233
- @assembler.track_new_work(sub_works.size)
234
- sub_works.each do |work|
235
- @queues.first.push(work)
236
- end
237
- end
238
- else
239
- # Handle regular result
240
- @assembler.add_result(result)
241
-
242
- # If this result generated sub-works, add them to the queue
243
- if result.respond_to?(:sub_works) && !result.sub_works.empty?
244
- @assembler.track_new_work(result.sub_works.size)
245
- result.sub_works.each do |work|
246
- @queues.first.push(work)
247
- end
248
- end
249
- end
250
-
251
- when :error
252
- work, error = data
253
- @assembler.add_failed_work(work, error)
254
- @assembler.work_completed
255
- end
256
-
257
- # Small sleep to prevent CPU spinning
258
- sleep 0.001
259
- end
260
-
261
- # Return the final assembled result
262
- @assembler.finalize
263
- end
264
- end
265
- end
266
-
267
- # Example usage: Document processing system
268
- if __FILE__ == $PROGRAM_NAME
269
- puts "Starting producer-subscriber example: Document Processing System"
270
- puts "This example simulates a document processing system where:"
271
- puts "1. Initial documents are broken down into sections"
272
- puts "2. Sections are further broken down into paragraphs"
273
- puts "3. Paragraphs are processed individually"
274
- puts "4. Results are assembled into a hierarchical structure"
275
- puts
276
-
277
- # Sample documents to process
278
- documents = [
279
- "Annual Report 2025",
280
- "Technical Documentation",
281
- "Research Paper"
282
- ]
283
-
284
- worker_count = 4
285
- puts "Using #{worker_count} workers to process #{documents.size} documents"
286
- puts
287
-
288
- start_time = Time.now
289
- supervisor = ProducerSubscriber::TreeSupervisor.new(documents, worker_count)
290
- result = supervisor.start
291
- end_time = Time.now
292
-
293
- puts "Processing Results:"
294
- puts "==================="
295
- puts result
296
- puts
297
- puts "Processing completed in #{end_time - start_time} seconds"
298
-
299
- supervisor.shutdown
300
- end
data/sample.rb DELETED
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require_relative 'fractor'
4
-
5
- # Client-specific worker implementation inheriting from Fractor::Worker
6
- class MyWorker < Fractor::Worker
7
- # This method is called by the Ractor to process the work
8
- # It should return a Fractor::WorkResult object
9
- # If there is an error, it should raise an exception
10
- # The Ractor will catch the exception and send it back to the main thread
11
- def process(work)
12
- puts "Working on '#{work.inspect}'"
13
-
14
- if work.input == 5
15
- # Return a Fractor::WorkResult for errors
16
- return Fractor::WorkResult.new(error: "Error processing work #{work.input}", work: work)
17
- end
18
-
19
- calculated = work.input * 2
20
- # Return a Fractor::WorkResult for success
21
- Fractor::WorkResult.new(result: calculated, work: work)
22
- end
23
- end
24
-
25
- # Client-specific work item implementation inheriting from Fractor::Work
26
- class MyWork < Fractor::Work
27
- def initialize(input)
28
- super
29
- end
30
-
31
- def to_s
32
- "MyWork: #{@input}"
33
- end
34
- end
35
-
36
- # --- Main Execution ---
37
- # This section demonstrates how to use the Fractor framework with custom
38
- # MyWorker and MyWork classes.
39
- if __FILE__ == $0
40
- # Create supervisor, passing the client-specific worker and work classes
41
- supervisor = Fractor::Supervisor.new(
42
- worker_class: MyWorker,
43
- work_class: MyWork,
44
- num_workers: 2 # Specify the number of worker Ractors
45
- )
46
-
47
- # Add work items (raw data) - the Supervisor will wrap these in MyWork objects
48
- work_items = (1..10).to_a
49
- supervisor.add_work(work_items)
50
-
51
- # Run the supervisor to start processing work
52
- supervisor.run
53
-
54
- puts "Processing complete."
55
- puts "Final Aggregated Results:"
56
- # Access the results aggregator from the supervisor
57
- puts supervisor.results.inspect
58
-
59
- # Print failed items directly from the Fractor::ResultAggregator's errors array
60
- failed_items = supervisor.results.errors # Access the errors array
61
- puts "\nFailed Work Items (#{failed_items.size}):"
62
- # Display each Fractor::WorkResult object in the errors array
63
- puts failed_items.map(&:to_s).join("\n") # Use to_s on the WorkResult objects
64
- end