fractor 0.1.2 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3fc9be0fb1d8993186e56bbef30b33396314e878f8a9ea3d31d79efd3c46f06
4
- data.tar.gz: 8edb3ff38f5c054d9c1e477bfb799605fd4827eb679dc10db224ad1eb45e8d64
3
+ metadata.gz: 842123f96e07a7da2fd523396b9c3caa74e39ff3bda280fa8473c306a26d809c
4
+ data.tar.gz: 6bb92bd2ad43a2a7a3fac8f50056c2a0a7e16d1b79e914dc390c458d3ee3ea97
5
5
  SHA512:
6
- metadata.gz: 17fbad9c566525c9b01ccd8f613a6fe2eb2be62298894fc4ae636736ad678f3d1c46fbf49cce5e84d6e3584cc0a5e31ce06f92a994f2a3b92e58cceedabafcbc
7
- data.tar.gz: a33d27eb70ffbd8769c6db1f5484788d0b187b5c1feb33ba65602579a2ecc17c3c14be6654ad74c414a63cf9b5d19a818bd462293eca0ca13bfd1f92272760f1
6
+ metadata.gz: 3f0613742f3e736514ef88cafde83a35d68e02178a034565b3c36aad00f7f6463fec3f4aad37bdc5dc65dd2c12c7c033b29009db97704e8c9cf4c67607f8f58a
7
+ data.tar.gz: d8e466f3244f0a88456f9c42388590a6c67e3e552b4b3e7d769217b32ddf12b2595264944241590e4c4f0ec098a79ac02bfe39622ba3d4f95cd037bd3d23e064
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-05-08 09:25:37 UTC using RuboCop version 1.75.5.
3
+ # on 2025-10-09 11:44:45 UTC using RuboCop version 1.75.4.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -14,11 +14,6 @@ Lint/ConstantDefinitionInBlock:
14
14
  - 'spec/fractor/integration_spec.rb'
15
15
  - 'spec/fractor/work_spec.rb'
16
16
 
17
- # Offense count: 1
18
- Lint/DuplicateMethods:
19
- Exclude:
20
- - 'lib/fractor/supervisor.rb'
21
-
22
17
  # Offense count: 1
23
18
  Lint/HashCompareByIdentity:
24
19
  Exclude:
@@ -30,43 +25,44 @@ Lint/MissingSuper:
30
25
  Exclude:
31
26
  - 'examples/specialized_workers/specialized_workers.rb'
32
27
 
33
- # Offense count: 3
28
+ # Offense count: 4
34
29
  Lint/RescueException:
35
30
  Exclude:
31
+ - 'lib/fractor/supervisor.rb'
36
32
  - 'lib/fractor/wrapped_ractor.rb'
37
33
 
38
- # Offense count: 18
34
+ # Offense count: 24
39
35
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
40
36
  Metrics/AbcSize:
41
37
  Max: 98
42
38
 
43
- # Offense count: 9
39
+ # Offense count: 15
44
40
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
45
41
  # AllowedMethods: refine
46
42
  Metrics/BlockLength:
47
- Max: 101
43
+ Max: 142
48
44
 
49
- # Offense count: 2
45
+ # Offense count: 4
50
46
  # Configuration parameters: CountComments, CountAsOne.
51
47
  Metrics/ClassLength:
52
- Max: 159
48
+ Max: 171
53
49
 
54
- # Offense count: 3
50
+ # Offense count: 9
55
51
  # Configuration parameters: AllowedMethods, AllowedPatterns.
56
52
  Metrics/CyclomaticComplexity:
57
53
  Max: 37
58
54
 
59
- # Offense count: 33
55
+ # Offense count: 46
60
56
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
61
57
  Metrics/MethodLength:
62
- Max: 67
58
+ Max: 78
63
59
 
64
60
  # Offense count: 1
65
61
  # Configuration parameters: Max, CountKeywordArgs.
66
62
  Metrics/ParameterLists:
67
63
  MaxOptionalParameters: 4
68
64
 
69
- # Offense count: 2
65
+ # Offense count: 6
70
66
  # Configuration parameters: AllowedMethods, AllowedPatterns.
71
67
  Metrics/PerceivedComplexity:
72
68
  Max: 37
@@ -84,13 +80,7 @@ Style/Documentation:
84
80
  - 'test/**/*'
85
81
  - 'examples/hierarchical_hasher/hierarchical_hasher.rb'
86
82
 
87
- # Offense count: 13
88
- # This cop supports safe autocorrection (--autocorrect).
89
- Style/IfUnlessModifier:
90
- Exclude:
91
- - 'lib/fractor/supervisor.rb'
92
-
93
- # Offense count: 6
83
+ # Offense count: 12
94
84
  # This cop supports safe autocorrection (--autocorrect).
95
85
  # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
96
86
  # URISchemes: http, https
data/README.adoc CHANGED
@@ -267,16 +267,25 @@ The Supervisor also manages the work queue and the ResultAggregator, which
267
267
  collects and organizes all results from the workers.
268
268
 
269
269
  To set up the Supervisor, you specify worker pools, each containing a Worker class
270
- and the number of workers to create. You can create multiple worker pools with
271
- different worker types to handle different kinds of work. Each worker pool can
272
- process any type of Work object that inherits from Fractor::Work.
270
+ and optionally the number of workers to create. If you don't specify `num_workers`,
271
+ Fractor will automatically detect the number of available processors on your system
272
+ and use that value. You can create multiple worker pools with different worker types
273
+ to handle different kinds of work. Each worker pool can process any type of Work
274
+ object that inherits from Fractor::Work.
273
275
 
274
276
  [source,ruby]
275
277
  ----
276
- # Create the supervisor
278
+ # Create the supervisor with auto-detected number of workers
277
279
  supervisor = Fractor::Supervisor.new(
278
280
  worker_pools: [
279
- { worker_class: MyWorker, num_workers: 4 } # One pool with 4 workers
281
+ { worker_class: MyWorker } # Number of workers auto-detected
282
+ ]
283
+ )
284
+
285
+ # Or explicitly specify the number of workers
286
+ supervisor = Fractor::Supervisor.new(
287
+ worker_pools: [
288
+ { worker_class: MyWorker, num_workers: 4 } # Explicitly use 4 workers
280
289
  ]
281
290
  )
282
291
 
@@ -619,6 +628,47 @@ supervisor = Fractor::Supervisor.new(
619
628
  )
620
629
  ----
621
630
 
631
+ ==== Worker auto-detection
632
+
633
+ Fractor automatically detects the number of available processors on your system
634
+ and uses that value when `num_workers` is not specified. This provides optimal
635
+ resource utilization across different deployment environments without requiring
636
+ manual configuration.
637
+
638
+ [source,ruby]
639
+ ----
640
+ # Auto-detect number of workers (recommended for most cases)
641
+ supervisor = Fractor::Supervisor.new(
642
+ worker_pools: [
643
+ { worker_class: MyWorker } # Will use number of available processors
644
+ ]
645
+ )
646
+
647
+ # Explicitly set number of workers (useful for specific requirements)
648
+ supervisor = Fractor::Supervisor.new(
649
+ worker_pools: [
650
+ { worker_class: MyWorker, num_workers: 4 } # Always use exactly 4 workers
651
+ ]
652
+ )
653
+
654
+ # Mix auto-detection and explicit configuration
655
+ supervisor = Fractor::Supervisor.new(
656
+ worker_pools: [
657
+ { worker_class: FastWorker }, # Auto-detected
658
+ { worker_class: HeavyWorker, num_workers: 2 } # Explicitly 2 workers
659
+ ]
660
+ )
661
+ ----
662
+
663
+ The auto-detection uses Ruby's `Etc.nprocessors` which returns the number of
664
+ available processors. If detection fails for any reason, it falls back to 2
665
+ workers.
666
+
667
+ [TIP]
668
+ * Use auto-detection for portable code that adapts to different environments
669
+ * Explicitly set `num_workers` when you need precise control over resource usage
670
+ * Consider system load and other factors when choosing explicit values
671
+
622
672
  ==== Adding work
623
673
 
624
674
  You can add work items individually or in batches:
@@ -897,8 +947,24 @@ Key features:
897
947
  * Simple Supervisor setup
898
948
  * Parallel processing of work items
899
949
  * Error handling and result aggregation
950
+ * Auto-detection of available processors
900
951
  * Graceful shutdown on completion
901
952
 
953
+ === Auto-detection example
954
+
955
+ The Auto-Detection Example (link:examples/auto_detection/[examples/auto_detection/])
956
+ demonstrates Fractor's automatic worker detection feature. It shows how to use
957
+ auto-detection, explicit configuration, and mixed approaches for controlling
958
+ the number of workers.
959
+
960
+ Key features:
961
+
962
+ * Automatic detection of available processors
963
+ * Comparison of auto-detection vs explicit configuration
964
+ * Mixed configuration with multiple worker pools
965
+ * Best practices for worker configuration
966
+ * Portable code that adapts to different environments
967
+
902
968
  === Hierarchical hasher
903
969
 
904
970
  The Hierarchical Hasher example
@@ -0,0 +1,52 @@
1
+ = Auto-Detection Example
2
+
3
+ This example demonstrates Fractor's automatic worker detection feature.
4
+
5
+ == Purpose
6
+
7
+ Shows how Fractor can automatically detect the number of available processors on your system and create the optimal number of workers without manual configuration.
8
+
9
+ == What This Demonstrates
10
+
11
+ * How Fractor automatically detects the number of available processors
12
+ * Comparison between auto-detection and explicit worker configuration
13
+ * Mixed configuration (some pools with auto-detection, some explicit)
14
+ * How to verify the number of workers being used
15
+
16
+ == When to Use Auto-Detection
17
+
18
+ * For portable code that adapts to different environments
19
+ * When you want optimal resource utilization without manual tuning
20
+ * For development where the number of cores varies across machines
21
+
22
+ == When to Set Explicit Values
23
+
24
+ * When you need precise control over resource usage
25
+ * For production environments with specific requirements
26
+ * When limiting workers due to memory or other constraints
27
+
28
+ == Running the Example
29
+
30
+ [source,shell]
31
+ ----
32
+ ruby examples/auto_detection/auto_detection.rb
33
+ ----
34
+
35
+ == Expected Output
36
+
37
+ The script will:
38
+
39
+ 1. Display the number of processors detected on your system
40
+ 2. Run three examples:
41
+ * Example 1: Auto-detection (uses all available processors)
42
+ * Example 2: Explicit configuration (uses exactly 4 workers)
43
+ * Example 3: Mixed configuration (combines both approaches)
44
+ 3. Show results from processing work items in parallel
45
+ 4. Provide a summary of benefits for each approach
46
+
47
+ == Key Takeaways
48
+
49
+ * Auto-detection provides automatic adaptation to different environments
50
+ * Explicit configuration provides precise control when needed
51
+ * You can mix both approaches in the same supervisor
52
+ * Best practice: use auto-detection for development, tune for production if needed
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # =============================================================================
5
+ # Auto-Detection Example
6
+ # =============================================================================
7
+ #
8
+ # This example demonstrates Fractor's automatic worker detection feature.
9
+ #
10
+ # WHAT THIS DEMONSTRATES:
11
+ # - How Fractor automatically detects the number of available processors
12
+ # - Comparison between auto-detection and explicit worker configuration
13
+ # - Mixed configuration (some pools with auto-detection, some explicit)
14
+ # - How to verify the number of workers being used
15
+ #
16
+ # WHEN TO USE AUTO-DETECTION:
17
+ # - For portable code that adapts to different environments
18
+ # - When you want optimal resource utilization without manual tuning
19
+ # - For development where the number of cores varies across machines
20
+ #
21
+ # WHEN TO SET EXPLICIT VALUES:
22
+ # - When you need precise control over resource usage
23
+ # - For production environments with specific requirements
24
+ # - When limiting workers due to memory or other constraints
25
+ #
26
+ # HOW TO RUN:
27
+ # ruby examples/auto_detection/auto_detection.rb
28
+ #
29
+ # WHAT TO EXPECT:
30
+ # - The script will show how many processors were auto-detected
31
+ # - It will create workers based on detection vs explicit configuration
32
+ # - Results will be processed in parallel across all workers
33
+ #
34
+ # =============================================================================
35
+
36
+ require_relative "../../lib/fractor"
37
+ require "etc"
38
+
39
+ # Simple work class for demonstration
40
+ class ComputeWork < Fractor::Work
41
+ def initialize(value)
42
+ super({ value: value })
43
+ end
44
+
45
+ def value
46
+ input[:value]
47
+ end
48
+
49
+ def to_s
50
+ "ComputeWork: #{value}"
51
+ end
52
+ end
53
+
54
+ # Simple worker that squares numbers
55
+ class ComputeWorker < Fractor::Worker
56
+ def process(work)
57
+ result = work.value * work.value
58
+ Fractor::WorkResult.new(result: result, work: work)
59
+ rescue StandardError => e
60
+ Fractor::WorkResult.new(error: e, work: work)
61
+ end
62
+ end
63
+
64
+ # =============================================================================
65
+ # DEMONSTRATION
66
+ # =============================================================================
67
+
68
+ puts "=" * 80
69
+ puts "Fractor Auto-Detection Example"
70
+ puts "=" * 80
71
+ puts
72
+
73
+ # Show system information
74
+ num_processors = Etc.nprocessors
75
+ puts "System Information:"
76
+ puts " Available processors: #{num_processors}"
77
+ puts
78
+
79
+ # Example 1: Auto-detection (recommended for most cases)
80
+ puts "-" * 80
81
+ puts "Example 1: Auto-Detection"
82
+ puts "-" * 80
83
+ puts "Creating supervisor WITHOUT specifying num_workers..."
84
+ puts "Fractor will automatically detect and use #{num_processors} workers"
85
+ puts
86
+
87
+ supervisor1 = Fractor::Supervisor.new(
88
+ worker_pools: [
89
+ { worker_class: ComputeWorker } # No num_workers specified
90
+ ]
91
+ )
92
+
93
+ # Add work items
94
+ work_items = (1..10).map { |i| ComputeWork.new(i) }
95
+ supervisor1.add_work_items(work_items)
96
+
97
+ puts "Processing 10 work items with auto-detected workers..."
98
+ supervisor1.run
99
+
100
+ puts "Results: #{supervisor1.results.results.map(&:result).sort.join(", ")}"
101
+ puts "✓ Auto-detection successful!"
102
+ puts
103
+
104
+ # Example 2: Explicit configuration
105
+ puts "-" * 80
106
+ puts "Example 2: Explicit Configuration"
107
+ puts "-" * 80
108
+ puts "Creating supervisor WITH explicit num_workers=4..."
109
+ puts
110
+
111
+ supervisor2 = Fractor::Supervisor.new(
112
+ worker_pools: [
113
+ { worker_class: ComputeWorker, num_workers: 4 }
114
+ ]
115
+ )
116
+
117
+ supervisor2.add_work_items((11..20).map { |i| ComputeWork.new(i) })
118
+
119
+ puts "Processing 10 work items with 4 explicitly configured workers..."
120
+ supervisor2.run
121
+
122
+ puts "Results: #{supervisor2.results.results.map(&:result).sort.join(", ")}"
123
+ puts "✓ Explicit configuration successful!"
124
+ puts
125
+
126
+ # Example 3: Mixed configuration
127
+ puts "-" * 80
128
+ puts "Example 3: Mixed Auto-Detection and Explicit Configuration"
129
+ puts "-" * 80
130
+ puts "Creating supervisor with multiple worker pools:"
131
+ puts " - Pool 1: Auto-detected workers"
132
+ puts " - Pool 2: 2 explicitly configured workers"
133
+ puts
134
+
135
+ supervisor3 = Fractor::Supervisor.new(
136
+ worker_pools: [
137
+ { worker_class: ComputeWorker }, # Auto-detected
138
+ { worker_class: ComputeWorker, num_workers: 2 } # Explicit
139
+ ]
140
+ )
141
+
142
+ supervisor3.add_work_items((21..30).map { |i| ComputeWork.new(i) })
143
+
144
+ puts "Processing 10 work items with mixed configuration..."
145
+ supervisor3.run
146
+
147
+ puts "Results: #{supervisor3.results.results.map(&:result).sort.join(", ")}"
148
+ puts "✓ Mixed configuration successful!"
149
+ puts
150
+
151
+ # Summary
152
+ puts "=" * 80
153
+ puts "Summary"
154
+ puts "=" * 80
155
+ puts
156
+ puts "Auto-detection provides:"
157
+ puts " ✓ Automatic adaptation to different environments"
158
+ puts " ✓ Optimal resource utilization by default"
159
+ puts " ✓ Less configuration needed"
160
+ puts " ✓ Portability across machines with different CPU counts"
161
+ puts
162
+ puts "Explicit configuration provides:"
163
+ puts " ✓ Precise control over worker count"
164
+ puts " ✓ Ability to limit resource usage"
165
+ puts " ✓ Predictable behavior in production"
166
+ puts
167
+ puts "Best practice: Use auto-detection for development and testing,"
168
+ puts " then tune explicitly for production if needed."
169
+ puts
170
+ puts "=" * 80
@@ -1,7 +1,38 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "fractor"
4
+ # =============================================================================
5
+ # Simple Example - Getting Started with Fractor
6
+ # =============================================================================
7
+ #
8
+ # This example demonstrates the basic usage of the Fractor framework.
9
+ #
10
+ # WHAT THIS DEMONSTRATES:
11
+ # - How to create a Work class (MyWork) to encapsulate work items
12
+ # - How to create a Worker class (MyWorker) to process work
13
+ # - How to set up a Supervisor to manage parallel processing
14
+ # - Basic error handling in workers
15
+ # - How to access and display results after processing
16
+ # - Auto-detection of available processors (num_workers not specified)
17
+ #
18
+ # KEY CONCEPTS:
19
+ # 1. Work Class: Inherits from Fractor::Work, stores input data
20
+ # 2. Worker Class: Inherits from Fractor::Worker, implements process() method
21
+ # 3. Supervisor: Manages worker Ractors and distributes work
22
+ # 4. WorkResult: Contains either successful results or errors
23
+ #
24
+ # HOW TO RUN:
25
+ # ruby examples/simple/sample.rb
26
+ #
27
+ # WHAT TO EXPECT:
28
+ # - Creates work items with values 1-10
29
+ # - Processes them in parallel using auto-detected number of workers
30
+ # - Value 5 intentionally produces an error for demonstration
31
+ # - Displays successful results and error information
32
+ #
33
+ # =============================================================================
34
+
35
+ require_relative "../../lib/fractor"
5
36
 
6
37
  # Client-specific work item implementation inheriting from Fractor::Work
7
38
  class MyWork < Fractor::Work
@@ -71,9 +102,10 @@ end
71
102
  # MyWorker and MyWork classes.
72
103
  if __FILE__ == $PROGRAM_NAME
73
104
  # Create supervisor, passing the client-specific worker class in a worker pool
105
+ # Note: num_workers is not specified, so it will auto-detect the number of available processors
74
106
  supervisor = Fractor::Supervisor.new(
75
107
  worker_pools: [
76
- { worker_class: MyWorker, num_workers: 2 } # Specify the worker class and number of worker Ractors
108
+ { worker_class: MyWorker } # Worker class without explicit num_workers uses auto-detection
77
109
  ]
78
110
  )
79
111
 
@@ -13,10 +13,10 @@ module Fractor
13
13
 
14
14
  def add_result(result)
15
15
  if result.success?
16
- puts "Work completed successfully: Result: #{result.result}"
16
+ puts "Work completed successfully: Result: #{result.result}" if ENV["FRACTOR_DEBUG"]
17
17
  @results << result
18
18
  else
19
- puts "Error processing work: #{result}"
19
+ puts "Error processing work: #{result}" if ENV["FRACTOR_DEBUG"]
20
20
  @errors << result
21
21
  end
22
22
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "etc"
4
+
3
5
  module Fractor
4
6
  # Supervises multiple WrappedRactors, distributes work, and aggregates results.
5
7
  class Supervisor
@@ -13,7 +15,7 @@ module Fractor
13
15
  def initialize(worker_pools: [], continuous_mode: false)
14
16
  @worker_pools = worker_pools.map do |pool_config|
15
17
  worker_class = pool_config[:worker_class]
16
- num_workers = pool_config[:num_workers] || 2
18
+ num_workers = pool_config[:num_workers] || detect_num_workers
17
19
 
18
20
  raise ArgumentError, "#{worker_class} must inherit from Fractor::Worker" unless worker_class < Fractor::Worker
19
21
 
@@ -46,9 +48,6 @@ module Fractor
46
48
  puts "Work item added. Initial work count: #{@total_work_count}, Queue size: #{@work_queue.size}"
47
49
  end
48
50
 
49
- # Alias for better naming
50
- alias add_work_item add_work_item
51
-
52
51
  # Adds multiple work items to the queue.
53
52
  # Each item must be an instance of Fractor::Work or a subclass.
54
53
  def add_work_items(works)
@@ -88,19 +87,32 @@ module Fractor
88
87
 
89
88
  # Sets up a signal handler for graceful shutdown (Ctrl+C).
90
89
  def setup_signal_handler
91
- # Need access to @workers within the trap block
90
+ # Store instance variables in local variables for the signal handler
92
91
  workers_ref = @workers
92
+
93
+ # Trap INT signal (Ctrl+C)
93
94
  Signal.trap("INT") do
94
- puts "\nCtrl+C received. Initiating immediate shutdown..."
95
- puts "Attempting to close worker Ractors..."
95
+ puts "\nCtrl+C received. Initiating immediate shutdown..." if ENV["FRACTOR_DEBUG"]
96
+
97
+ # Set running to false to break the main loop
98
+ @running = false
99
+
100
+ puts "Sending shutdown message to all Ractors..." if ENV["FRACTOR_DEBUG"]
101
+
102
+ # Send shutdown message to each worker Ractor
96
103
  workers_ref.each do |w|
97
- w.close # Use the close method of WrappedRactor
98
- puts "Closed Ractor: #{w.name}"
104
+ w.send(:shutdown)
105
+ puts "Sent shutdown to Ractor: #{w.name}" if ENV["FRACTOR_DEBUG"]
99
106
  rescue StandardError => e
100
- puts "Error closing Ractor #{w.name}: #{e.message}"
107
+ puts "Error sending shutdown to Ractor #{w.name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
101
108
  end
102
- puts "Exiting now."
103
- exit(1) # Exit immediately
109
+
110
+ puts "Exiting now." if ENV["FRACTOR_DEBUG"]
111
+ exit!(1) # Use exit! to exit immediately without running at_exit handlers
112
+ rescue Exception => e
113
+ puts "Error in signal handler: #{e.class}: #{e.message}" if ENV["FRACTOR_DEBUG"]
114
+ puts e.backtrace.join("\n") if ENV["FRACTOR_DEBUG"]
115
+ exit!(1)
104
116
  end
105
117
  end
106
118
 
@@ -208,11 +220,22 @@ module Fractor
208
220
  # Stop the supervisor (for continuous mode)
209
221
  def stop
210
222
  @running = false
211
- puts "Stopping supervisor..."
223
+ puts "Stopping supervisor..." if ENV["FRACTOR_DEBUG"]
212
224
  end
213
225
 
214
226
  private
215
227
 
228
+ # Detects the number of available processors on the system.
229
+ # Returns the number of processors, or 2 as a fallback if detection fails.
230
+ def detect_num_workers
231
+ num_processors = Etc.nprocessors
232
+ puts "Auto-detected #{num_processors} available processors" if ENV["FRACTOR_DEBUG"]
233
+ num_processors
234
+ rescue StandardError => e
235
+ puts "Failed to detect processors: #{e.message}. Using default of 2 workers." if ENV["FRACTOR_DEBUG"]
236
+ 2
237
+ end
238
+
216
239
  # Helper method to send the next available work item to a specific Ractor.
217
240
  def send_next_work_if_available(wrapped_ractor)
218
241
  # Ensure the wrapped_ractor instance is valid and its underlying ractor is not closed
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Fractor
4
4
  # Fractor version
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.4"
6
6
  end
@@ -9,7 +9,7 @@ module Fractor
9
9
  # Initializes the WrappedRactor with a name and the Worker class to instantiate.
10
10
  # The worker_class parameter allows flexibility in specifying the worker type.
11
11
  def initialize(name, worker_class)
12
- puts "Creating Ractor #{name} with worker #{worker_class}"
12
+ puts "Creating Ractor #{name} with worker #{worker_class}" if ENV["FRACTOR_DEBUG"]
13
13
  @name = name
14
14
  @worker_class = worker_class # Store the worker class
15
15
  @ractor = nil # Initialize ractor as nil
@@ -17,10 +17,10 @@ module Fractor
17
17
 
18
18
  # Starts the underlying Ractor.
19
19
  def start
20
- puts "Starting Ractor #{@name}"
20
+ puts "Starting Ractor #{@name}" if ENV["FRACTOR_DEBUG"]
21
21
  # Pass worker_class to the Ractor block
22
22
  @ractor = Ractor.new(@name, @worker_class) do |name, worker_cls|
23
- puts "Ractor #{name} started with worker class #{worker_cls}"
23
+ puts "Ractor #{name} started with worker class #{worker_cls}" if ENV["FRACTOR_DEBUG"]
24
24
  # Yield an initialization message
25
25
  Ractor.yield({ type: :initialize, processor: name })
26
26
 
@@ -29,19 +29,26 @@ module Fractor
29
29
 
30
30
  loop do
31
31
  # Ractor.receive will block until a message is received
32
- puts "Waiting for work in #{name}"
32
+ puts "Waiting for work in #{name}" if ENV["FRACTOR_DEBUG"]
33
33
  work = Ractor.receive
34
- puts "Received work #{work.inspect} in #{name}"
34
+
35
+ # Handle shutdown message
36
+ if work == :shutdown
37
+ puts "Received shutdown message in Ractor #{name}, terminating..." if ENV["FRACTOR_DEBUG"]
38
+ break
39
+ end
40
+
41
+ puts "Received work #{work.inspect} in #{name}" if ENV["FRACTOR_DEBUG"]
35
42
 
36
43
  begin
37
44
  # Process the work using the instantiated worker
38
45
  result = worker.process(work)
39
- puts "Sending result #{result.inspect} from Ractor #{name}"
46
+ puts "Sending result #{result.inspect} from Ractor #{name}" if ENV["FRACTOR_DEBUG"]
40
47
  # Yield the result back
41
48
  Ractor.yield({ type: :result, result: result, processor: name })
42
49
  rescue StandardError => e
43
50
  # Handle errors during processing
44
- puts "Error processing work #{work.inspect} in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}"
51
+ puts "Error processing work #{work.inspect} in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}" if ENV["FRACTOR_DEBUG"]
45
52
  # Yield an error message back
46
53
  # Ensure the original work object is included in the error result
47
54
  error_result = Fractor::WorkResult.new(error: e.message, work: work)
@@ -49,14 +56,14 @@ module Fractor
49
56
  end
50
57
  end
51
58
  rescue Ractor::ClosedError
52
- puts "Ractor #{name} closed."
59
+ puts "Ractor #{name} closed." if ENV["FRACTOR_DEBUG"]
53
60
  rescue StandardError => e
54
- puts "Unexpected error in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}"
61
+ puts "Unexpected error in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}" if ENV["FRACTOR_DEBUG"]
55
62
  # Optionally yield a critical error message if needed
56
63
  ensure
57
- puts "Ractor #{name} shutting down."
64
+ puts "Ractor #{name} shutting down." if ENV["FRACTOR_DEBUG"]
58
65
  end
59
- puts "Ractor #{@name} instance created: #{@ractor}"
66
+ puts "Ractor #{@name} instance created: #{@ractor}" if ENV["FRACTOR_DEBUG"]
60
67
  end
61
68
 
62
69
  # Sends work to the Ractor if it's active.
@@ -66,11 +73,11 @@ module Fractor
66
73
  @ractor.send(work)
67
74
  true
68
75
  rescue Exception => e
69
- puts "Warning: Error sending work to Ractor #{@name}: #{e.message}"
76
+ puts "Warning: Error sending work to Ractor #{@name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
70
77
  false
71
78
  end
72
79
  else
73
- puts "Warning: Attempted to send work to nil Ractor #{@name}"
80
+ puts "Warning: Attempted to send work to nil Ractor #{@name}" if ENV["FRACTOR_DEBUG"]
74
81
  false
75
82
  end
76
83
  end
@@ -108,7 +115,7 @@ module Fractor
108
115
 
109
116
  true
110
117
  rescue Exception => e
111
- puts "Warning: Error closing Ractor #{@name}: #{e.message}"
118
+ puts "Warning: Error closing Ractor #{@name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
112
119
  # Consider it closed even if there was an error
113
120
  @ractor = nil
114
121
  true
@@ -131,7 +138,7 @@ module Fractor
131
138
  false
132
139
  rescue Exception => e
133
140
  # If we get an exception, the Ractor is likely terminated
134
- puts "Ractor #{@name} appears to be terminated: #{e.message}"
141
+ puts "Ractor #{@name} appears to be terminated: #{e.message}" if ENV["FRACTOR_DEBUG"]
135
142
  @ractor = nil
136
143
  true
137
144
  end
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.2
4
+ version: 0.1.4
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-08 00:00:00.000000000 Z
11
+ date: 2025-10-09 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.
@@ -24,6 +24,8 @@ files:
24
24
  - CODE_OF_CONDUCT.md
25
25
  - README.adoc
26
26
  - Rakefile
27
+ - examples/auto_detection/README.adoc
28
+ - examples/auto_detection/auto_detection.rb
27
29
  - examples/hierarchical_hasher/README.adoc
28
30
  - examples/hierarchical_hasher/hierarchical_hasher.rb
29
31
  - examples/multi_work_type/README.adoc