fractor 0.1.3 → 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: b51bfbe1d8aac8575dcd85deb88b286b8f3b26e391a228e01d43f3a466d91f26
4
- data.tar.gz: '08198a0b664d2ddfa9cbef0a173f1672f9584dba1700f82374b0b9916cad91a6'
3
+ metadata.gz: 842123f96e07a7da2fd523396b9c3caa74e39ff3bda280fa8473c306a26d809c
4
+ data.tar.gz: 6bb92bd2ad43a2a7a3fac8f50056c2a0a7e16d1b79e914dc390c458d3ee3ea97
5
5
  SHA512:
6
- metadata.gz: af7c2e42e19b28ced53b9e3aecf37409b9aff3c92d8829a0835312a4a5852069530b36f57c01c84727d09ad9de3b308ecfa05a5a812454402a300219938bce19
7
- data.tar.gz: e08de69150ff8d528d481fcb1e872cd2d274e6b332f6d2ad3ad3b879e13328d50af3fae3294aecd81851dc7d6f3f6cc317570a7978b0f1a8adcd142d4b18a8f5
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
 
@@ -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)
@@ -226,6 +225,17 @@ module Fractor
226
225
 
227
226
  private
228
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
+
229
239
  # Helper method to send the next available work item to a specific Ractor.
230
240
  def send_next_work_if_available(wrapped_ractor)
231
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.3"
5
+ VERSION = "0.1.4"
6
6
  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.3
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