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 +4 -4
- data/.rubocop_todo.yml +13 -23
- data/README.adoc +71 -5
- data/examples/auto_detection/README.adoc +52 -0
- data/examples/auto_detection/auto_detection.rb +170 -0
- data/examples/simple/sample.rb +34 -2
- data/lib/fractor/result_aggregator.rb +2 -2
- data/lib/fractor/supervisor.rb +36 -13
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/wrapped_ractor.rb +22 -15
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 842123f96e07a7da2fd523396b9c3caa74e39ff3bda280fa8473c306a26d809c
|
4
|
+
data.tar.gz: 6bb92bd2ad43a2a7a3fac8f50056c2a0a7e16d1b79e914dc390c458d3ee3ea97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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-
|
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:
|
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:
|
34
|
+
# Offense count: 24
|
39
35
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
40
36
|
Metrics/AbcSize:
|
41
37
|
Max: 98
|
42
38
|
|
43
|
-
# Offense count:
|
39
|
+
# Offense count: 15
|
44
40
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
45
41
|
# AllowedMethods: refine
|
46
42
|
Metrics/BlockLength:
|
47
|
-
Max:
|
43
|
+
Max: 142
|
48
44
|
|
49
|
-
# Offense count:
|
45
|
+
# Offense count: 4
|
50
46
|
# Configuration parameters: CountComments, CountAsOne.
|
51
47
|
Metrics/ClassLength:
|
52
|
-
Max:
|
48
|
+
Max: 171
|
53
49
|
|
54
|
-
# Offense count:
|
50
|
+
# Offense count: 9
|
55
51
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
56
52
|
Metrics/CyclomaticComplexity:
|
57
53
|
Max: 37
|
58
54
|
|
59
|
-
# Offense count:
|
55
|
+
# Offense count: 46
|
60
56
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
61
57
|
Metrics/MethodLength:
|
62
|
-
Max:
|
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:
|
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:
|
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.
|
271
|
-
|
272
|
-
|
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
|
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
|
data/examples/simple/sample.rb
CHANGED
@@ -1,7 +1,38 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
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
|
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
|
|
data/lib/fractor/supervisor.rb
CHANGED
@@ -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] ||
|
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
|
-
#
|
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
|
-
|
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.
|
98
|
-
puts "
|
104
|
+
w.send(:shutdown)
|
105
|
+
puts "Sent shutdown to Ractor: #{w.name}" if ENV["FRACTOR_DEBUG"]
|
99
106
|
rescue StandardError => e
|
100
|
-
puts "Error
|
107
|
+
puts "Error sending shutdown to Ractor #{w.name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
|
101
108
|
end
|
102
|
-
|
103
|
-
|
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
|
data/lib/fractor/version.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|