fractor 0.1.4 → 0.1.7
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-https---raw-githubusercontent-com-riboseinc-oss-guides-main-ci-rubocop-yml +552 -0
- data/.rubocop.yml +14 -8
- data/.rubocop_todo.yml +284 -43
- data/README.adoc +111 -950
- data/docs/.lycheeignore +16 -0
- data/docs/Gemfile +24 -0
- data/docs/README.md +157 -0
- data/docs/_config.yml +151 -0
- data/docs/_features/error-handling.adoc +1192 -0
- data/docs/_features/index.adoc +80 -0
- data/docs/_features/monitoring.adoc +589 -0
- data/docs/_features/signal-handling.adoc +202 -0
- data/docs/_features/workflows.adoc +1235 -0
- data/docs/_guides/continuous-mode.adoc +736 -0
- data/docs/_guides/cookbook.adoc +1133 -0
- data/docs/_guides/index.adoc +55 -0
- data/docs/_guides/pipeline-mode.adoc +730 -0
- data/docs/_guides/troubleshooting.adoc +358 -0
- data/docs/_pages/architecture.adoc +1390 -0
- data/docs/_pages/core-concepts.adoc +1392 -0
- data/docs/_pages/design-principles.adoc +862 -0
- data/docs/_pages/getting-started.adoc +290 -0
- data/docs/_pages/installation.adoc +143 -0
- data/docs/_reference/api.adoc +1080 -0
- data/docs/_reference/error-reporting.adoc +670 -0
- data/docs/_reference/examples.adoc +181 -0
- data/docs/_reference/index.adoc +96 -0
- data/docs/_reference/troubleshooting.adoc +862 -0
- data/docs/_tutorials/complex-workflows.adoc +1022 -0
- data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
- data/docs/_tutorials/first-application.adoc +384 -0
- data/docs/_tutorials/index.adoc +48 -0
- data/docs/_tutorials/long-running-services.adoc +931 -0
- data/docs/assets/images/favicon-16.png +0 -0
- data/docs/assets/images/favicon-32.png +0 -0
- data/docs/assets/images/favicon-48.png +0 -0
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/images/favicon.svg +45 -0
- data/docs/assets/images/fractor-icon.svg +49 -0
- data/docs/assets/images/fractor-logo.svg +61 -0
- data/docs/index.adoc +131 -0
- data/docs/lychee.toml +39 -0
- data/examples/api_aggregator/README.adoc +627 -0
- data/examples/api_aggregator/api_aggregator.rb +376 -0
- data/examples/auto_detection/README.adoc +407 -29
- data/examples/auto_detection/auto_detection.rb +9 -9
- data/examples/continuous_chat_common/message_protocol.rb +53 -0
- data/examples/continuous_chat_fractor/README.adoc +217 -0
- data/examples/continuous_chat_fractor/chat_client.rb +303 -0
- data/examples/continuous_chat_fractor/chat_common.rb +83 -0
- data/examples/continuous_chat_fractor/chat_server.rb +167 -0
- data/examples/continuous_chat_fractor/simulate.rb +345 -0
- data/examples/continuous_chat_server/README.adoc +135 -0
- data/examples/continuous_chat_server/chat_client.rb +303 -0
- data/examples/continuous_chat_server/chat_server.rb +359 -0
- data/examples/continuous_chat_server/simulate.rb +343 -0
- data/examples/error_reporting.rb +207 -0
- data/examples/file_processor/README.adoc +170 -0
- data/examples/file_processor/file_processor.rb +615 -0
- data/examples/file_processor/sample_files/invalid.csv +1 -0
- data/examples/file_processor/sample_files/orders.xml +24 -0
- data/examples/file_processor/sample_files/products.json +23 -0
- data/examples/file_processor/sample_files/users.csv +6 -0
- data/examples/hierarchical_hasher/README.adoc +629 -41
- data/examples/hierarchical_hasher/hierarchical_hasher.rb +12 -8
- data/examples/image_processor/README.adoc +610 -0
- data/examples/image_processor/image_processor.rb +349 -0
- data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
- data/examples/image_processor/test_images/sample_1.png +1 -0
- data/examples/image_processor/test_images/sample_10.png +1 -0
- data/examples/image_processor/test_images/sample_2.png +1 -0
- data/examples/image_processor/test_images/sample_3.png +1 -0
- data/examples/image_processor/test_images/sample_4.png +1 -0
- data/examples/image_processor/test_images/sample_5.png +1 -0
- data/examples/image_processor/test_images/sample_6.png +1 -0
- data/examples/image_processor/test_images/sample_7.png +1 -0
- data/examples/image_processor/test_images/sample_8.png +1 -0
- data/examples/image_processor/test_images/sample_9.png +1 -0
- data/examples/log_analyzer/README.adoc +662 -0
- data/examples/log_analyzer/log_analyzer.rb +579 -0
- data/examples/log_analyzer/sample_logs/apache.log +20 -0
- data/examples/log_analyzer/sample_logs/json.log +15 -0
- data/examples/log_analyzer/sample_logs/nginx.log +15 -0
- data/examples/log_analyzer/sample_logs/rails.log +29 -0
- data/examples/multi_work_type/README.adoc +576 -26
- data/examples/multi_work_type/multi_work_type.rb +30 -29
- data/examples/performance_monitoring.rb +120 -0
- data/examples/pipeline_processing/README.adoc +740 -26
- data/examples/pipeline_processing/pipeline_processing.rb +16 -16
- data/examples/priority_work_example.rb +155 -0
- data/examples/producer_subscriber/README.adoc +889 -46
- data/examples/producer_subscriber/producer_subscriber.rb +20 -16
- data/examples/scatter_gather/README.adoc +829 -27
- data/examples/scatter_gather/scatter_gather.rb +29 -28
- data/examples/simple/README.adoc +347 -0
- data/examples/simple/sample.rb +5 -5
- data/examples/specialized_workers/README.adoc +622 -26
- data/examples/specialized_workers/specialized_workers.rb +88 -45
- data/examples/stream_processor/README.adoc +206 -0
- data/examples/stream_processor/stream_processor.rb +284 -0
- data/examples/web_scraper/README.adoc +625 -0
- data/examples/web_scraper/web_scraper.rb +285 -0
- data/examples/workflow/README.adoc +406 -0
- data/examples/workflow/circuit_breaker/README.adoc +360 -0
- data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
- data/examples/workflow/conditional/README.adoc +483 -0
- data/examples/workflow/conditional/conditional_workflow.rb +215 -0
- data/examples/workflow/dead_letter_queue/README.adoc +374 -0
- data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
- data/examples/workflow/fan_out/README.adoc +381 -0
- data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
- data/examples/workflow/retry/README.adoc +248 -0
- data/examples/workflow/retry/retry_workflow.rb +195 -0
- data/examples/workflow/simple_linear/README.adoc +267 -0
- data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
- data/examples/workflow/simplified/README.adoc +329 -0
- data/examples/workflow/simplified/simplified_workflow.rb +222 -0
- data/exe/fractor +10 -0
- data/lib/fractor/cli.rb +288 -0
- data/lib/fractor/configuration.rb +307 -0
- data/lib/fractor/continuous_server.rb +183 -0
- data/lib/fractor/error_formatter.rb +72 -0
- data/lib/fractor/error_report_generator.rb +152 -0
- data/lib/fractor/error_reporter.rb +244 -0
- data/lib/fractor/error_statistics.rb +147 -0
- data/lib/fractor/execution_tracer.rb +162 -0
- data/lib/fractor/logger.rb +230 -0
- data/lib/fractor/main_loop_handler.rb +406 -0
- data/lib/fractor/main_loop_handler3.rb +135 -0
- data/lib/fractor/main_loop_handler4.rb +299 -0
- data/lib/fractor/performance_metrics_collector.rb +181 -0
- data/lib/fractor/performance_monitor.rb +215 -0
- data/lib/fractor/performance_report_generator.rb +202 -0
- data/lib/fractor/priority_work.rb +93 -0
- data/lib/fractor/priority_work_queue.rb +189 -0
- data/lib/fractor/result_aggregator.rb +33 -1
- data/lib/fractor/shutdown_handler.rb +168 -0
- data/lib/fractor/signal_handler.rb +80 -0
- data/lib/fractor/supervisor.rb +430 -144
- data/lib/fractor/supervisor_logger.rb +88 -0
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/work.rb +12 -0
- data/lib/fractor/work_distribution_manager.rb +151 -0
- data/lib/fractor/work_queue.rb +88 -0
- data/lib/fractor/work_result.rb +181 -9
- data/lib/fractor/worker.rb +75 -1
- data/lib/fractor/workflow/builder.rb +210 -0
- data/lib/fractor/workflow/chain_builder.rb +169 -0
- data/lib/fractor/workflow/circuit_breaker.rb +183 -0
- data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
- data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
- data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
- data/lib/fractor/workflow/execution_hooks.rb +39 -0
- data/lib/fractor/workflow/execution_strategy.rb +225 -0
- data/lib/fractor/workflow/execution_trace.rb +134 -0
- data/lib/fractor/workflow/helpers.rb +191 -0
- data/lib/fractor/workflow/job.rb +290 -0
- data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
- data/lib/fractor/workflow/logger.rb +110 -0
- data/lib/fractor/workflow/pre_execution_context.rb +193 -0
- data/lib/fractor/workflow/retry_config.rb +156 -0
- data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
- data/lib/fractor/workflow/retry_strategy.rb +93 -0
- data/lib/fractor/workflow/structured_logger.rb +30 -0
- data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
- data/lib/fractor/workflow/visualizer.rb +211 -0
- data/lib/fractor/workflow/workflow_context.rb +132 -0
- data/lib/fractor/workflow/workflow_executor.rb +669 -0
- data/lib/fractor/workflow/workflow_result.rb +55 -0
- data/lib/fractor/workflow/workflow_validator.rb +295 -0
- data/lib/fractor/workflow.rb +333 -0
- data/lib/fractor/wrapped_ractor.rb +66 -91
- data/lib/fractor/wrapped_ractor3.rb +161 -0
- data/lib/fractor/wrapped_ractor4.rb +242 -0
- data/lib/fractor.rb +93 -3
- metadata +192 -6
- data/tests/sample.rb.bak +0 -309
- data/tests/sample_working.rb.bak +0 -209
|
@@ -1,31 +1,158 @@
|
|
|
1
1
|
= Auto-Detection Example
|
|
2
2
|
|
|
3
|
-
This example demonstrates Fractor's automatic worker detection feature.
|
|
4
|
-
|
|
5
3
|
== Purpose
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
Demonstrates Fractor's automatic worker detection feature, showing how the framework adapts to different hardware environments by automatically detecting and utilizing available CPU cores.
|
|
6
|
+
|
|
7
|
+
== Focus
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This example focuses on demonstrating:
|
|
10
10
|
|
|
11
|
-
*
|
|
11
|
+
* Automatic detection of available processors using `Etc.nprocessors`
|
|
12
12
|
* Comparison between auto-detection and explicit worker configuration
|
|
13
|
-
* Mixed configuration (
|
|
14
|
-
*
|
|
13
|
+
* Mixed configuration strategies (combining both approaches)
|
|
14
|
+
* Resource utilization optimization without manual tuning
|
|
15
|
+
* Portable code that adapts to different environments
|
|
16
|
+
|
|
17
|
+
== Architecture
|
|
18
|
+
|
|
19
|
+
.Auto-Detection vs Explicit Configuration
|
|
20
|
+
[source]
|
|
21
|
+
----
|
|
22
|
+
System Hardware
|
|
23
|
+
│
|
|
24
|
+
├─→ Etc.nprocessors
|
|
25
|
+
│ │
|
|
26
|
+
│ └─→ Returns: 8 (example)
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
┌─────────────────────────────────────────────┐
|
|
30
|
+
│ Fractor Supervisor │
|
|
31
|
+
└─────────────────────────────────────────────┘
|
|
32
|
+
│
|
|
33
|
+
├─→ Auto-Detection Mode
|
|
34
|
+
│ │
|
|
35
|
+
│ └─→ Worker Pool { worker_class: ComputeWorker }
|
|
36
|
+
│ │
|
|
37
|
+
│ └─→ Creates 8 Ractors (auto-detected)
|
|
38
|
+
│ │
|
|
39
|
+
│ ├─→ Ractor 1 (ComputeWorker)
|
|
40
|
+
│ ├─→ Ractor 2 (ComputeWorker)
|
|
41
|
+
│ ├─→ Ractor 3 (ComputeWorker)
|
|
42
|
+
│ ├─→ ...
|
|
43
|
+
│ └─→ Ractor 8 (ComputeWorker)
|
|
44
|
+
│
|
|
45
|
+
└─→ Explicit Configuration Mode
|
|
46
|
+
│
|
|
47
|
+
└─→ Worker Pool { worker_class: ComputeWorker, num_workers: 4 }
|
|
48
|
+
│
|
|
49
|
+
└─→ Creates 4 Ractors (explicit)
|
|
50
|
+
│
|
|
51
|
+
├─→ Ractor 1 (ComputeWorker)
|
|
52
|
+
├─→ Ractor 2 (ComputeWorker)
|
|
53
|
+
├─→ Ractor 3 (ComputeWorker)
|
|
54
|
+
└─→ Ractor 4 (ComputeWorker)
|
|
55
|
+
----
|
|
56
|
+
|
|
57
|
+
.Resource Utilization Comparison
|
|
58
|
+
[source]
|
|
59
|
+
----
|
|
60
|
+
Machine A (4 cores) Machine B (16 cores)
|
|
61
|
+
────────────────────────────────────────────────────────
|
|
15
62
|
|
|
16
|
-
|
|
63
|
+
Auto-Detection:
|
|
64
|
+
4 workers created 16 workers created
|
|
65
|
+
100% CPU utilization 100% CPU utilization
|
|
66
|
+
✓ Optimal ✓ Optimal
|
|
17
67
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
68
|
+
Explicit (num_workers: 4):
|
|
69
|
+
4 workers created 4 workers created
|
|
70
|
+
100% CPU utilization 25% CPU utilization
|
|
71
|
+
✓ Optimal ✗ Underutilized
|
|
21
72
|
|
|
22
|
-
|
|
73
|
+
Explicit (num_workers: 8):
|
|
74
|
+
8 workers created 8 workers created
|
|
75
|
+
200% oversubscribed 50% CPU utilization
|
|
76
|
+
✗ Oversubscribed ✗ Underutilized
|
|
77
|
+
----
|
|
78
|
+
|
|
79
|
+
== Key Components
|
|
80
|
+
|
|
81
|
+
=== Auto-Detection Configuration
|
|
82
|
+
|
|
83
|
+
Create supervisor without specifying `num_workers`:
|
|
84
|
+
|
|
85
|
+
[source,ruby]
|
|
86
|
+
----
|
|
87
|
+
supervisor = Fractor::Supervisor.new(
|
|
88
|
+
worker_pools: [
|
|
89
|
+
{ worker_class: ComputeWorker } # <1>
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
----
|
|
93
|
+
<1> Omitting `num_workers` triggers auto-detection
|
|
23
94
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
95
|
+
Fractor internally uses:
|
|
96
|
+
|
|
97
|
+
[source,ruby]
|
|
98
|
+
----
|
|
99
|
+
require 'etc'
|
|
100
|
+
|
|
101
|
+
num_workers = Etc.nprocessors # <1>
|
|
102
|
+
# Creates num_workers Ractor instances
|
|
103
|
+
----
|
|
104
|
+
<1> Returns the number of available processors
|
|
105
|
+
|
|
106
|
+
=== Explicit Configuration
|
|
107
|
+
|
|
108
|
+
Specify exact number of workers:
|
|
109
|
+
|
|
110
|
+
[source,ruby]
|
|
111
|
+
----
|
|
112
|
+
supervisor = Fractor::Supervisor.new(
|
|
113
|
+
worker_pools: [
|
|
114
|
+
{ worker_class: ComputeWorker, num_workers: 4 } # <1>
|
|
115
|
+
]
|
|
116
|
+
)
|
|
117
|
+
----
|
|
118
|
+
<1> Always creates exactly 4 workers regardless of hardware
|
|
119
|
+
|
|
120
|
+
=== Mixed Configuration
|
|
121
|
+
|
|
122
|
+
Combine both approaches in different worker pools:
|
|
123
|
+
|
|
124
|
+
[source,ruby]
|
|
125
|
+
----
|
|
126
|
+
supervisor = Fractor::Supervisor.new(
|
|
127
|
+
worker_pools: [
|
|
128
|
+
{ worker_class: FastWorker }, # <1>
|
|
129
|
+
{ worker_class: SlowWorker, num_workers: 2 } # <2>
|
|
130
|
+
]
|
|
131
|
+
)
|
|
132
|
+
----
|
|
133
|
+
<1> Auto-detects optimal workers for FastWorker
|
|
134
|
+
<2> Limits SlowWorker to 2 workers (e.g., memory intensive)
|
|
27
135
|
|
|
28
|
-
|
|
136
|
+
=== Worker Implementation
|
|
137
|
+
|
|
138
|
+
Simple worker for demonstration:
|
|
139
|
+
|
|
140
|
+
[source,ruby]
|
|
141
|
+
----
|
|
142
|
+
class ComputeWorker < Fractor::Worker
|
|
143
|
+
def process(work)
|
|
144
|
+
result = work.value * work.value # <1>
|
|
145
|
+
Fractor::WorkResult.new(result: result, work: work)
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
Fractor::WorkResult.new(error: e, work: work)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
----
|
|
151
|
+
<1> Simple computation (squares the input value)
|
|
152
|
+
|
|
153
|
+
== Usage
|
|
154
|
+
|
|
155
|
+
Run the example from the project root:
|
|
29
156
|
|
|
30
157
|
[source,shell]
|
|
31
158
|
----
|
|
@@ -34,19 +161,270 @@ ruby examples/auto_detection/auto_detection.rb
|
|
|
34
161
|
|
|
35
162
|
== Expected Output
|
|
36
163
|
|
|
37
|
-
|
|
164
|
+
[example]
|
|
165
|
+
====
|
|
166
|
+
[source]
|
|
167
|
+
----
|
|
168
|
+
================================================================================
|
|
169
|
+
Fractor Auto-Detection Example
|
|
170
|
+
================================================================================
|
|
171
|
+
|
|
172
|
+
System Information:
|
|
173
|
+
Available processors: 8
|
|
174
|
+
|
|
175
|
+
--------------------------------------------------------------------------------
|
|
176
|
+
Example 1: Auto-Detection
|
|
177
|
+
--------------------------------------------------------------------------------
|
|
178
|
+
Creating supervisor WITHOUT specifying num_workers...
|
|
179
|
+
Fractor will automatically detect and use 8 workers
|
|
180
|
+
|
|
181
|
+
Processing 10 work items with auto-detected workers...
|
|
182
|
+
Results: 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
|
|
183
|
+
✓ Auto-detection successful!
|
|
184
|
+
|
|
185
|
+
--------------------------------------------------------------------------------
|
|
186
|
+
Example 2: Explicit Configuration
|
|
187
|
+
--------------------------------------------------------------------------------
|
|
188
|
+
Creating supervisor WITH explicit num_workers=4...
|
|
189
|
+
|
|
190
|
+
Processing 10 work items with 4 explicitly configured workers...
|
|
191
|
+
Results: 121, 144, 169, 196, 225, 256, 289, 324, 361, 400
|
|
192
|
+
✓ Explicit configuration successful!
|
|
193
|
+
|
|
194
|
+
--------------------------------------------------------------------------------
|
|
195
|
+
Example 3: Mixed Auto-Detection and Explicit Configuration
|
|
196
|
+
--------------------------------------------------------------------------------
|
|
197
|
+
Creating supervisor with multiple worker pools:
|
|
198
|
+
- Pool 1: Auto-detected workers
|
|
199
|
+
- Pool 2: 2 explicitly configured workers
|
|
200
|
+
|
|
201
|
+
Processing 10 work items with mixed configuration...
|
|
202
|
+
Results: 441, 484, 529, 576, 625, 676, 729, 784, 841, 900
|
|
203
|
+
✓ Mixed configuration successful!
|
|
204
|
+
|
|
205
|
+
================================================================================
|
|
206
|
+
Summary
|
|
207
|
+
================================================================================
|
|
208
|
+
|
|
209
|
+
Auto-detection provides:
|
|
210
|
+
✓ Automatic adaptation to different environments
|
|
211
|
+
✓ Optimal resource utilization by default
|
|
212
|
+
✓ Less configuration needed
|
|
213
|
+
✓ Portability across machines with different CPU counts
|
|
214
|
+
|
|
215
|
+
Explicit configuration provides:
|
|
216
|
+
✓ Precise control over worker count
|
|
217
|
+
✓ Ability to limit resource usage
|
|
218
|
+
✓ Predictable behavior in production
|
|
219
|
+
|
|
220
|
+
Best practice: Use auto-detection for development and testing,
|
|
221
|
+
then tune explicitly for production if needed.
|
|
222
|
+
|
|
223
|
+
================================================================================
|
|
224
|
+
----
|
|
225
|
+
====
|
|
226
|
+
|
|
227
|
+
== Learning Points
|
|
228
|
+
|
|
229
|
+
=== When to Use Auto-Detection
|
|
230
|
+
|
|
231
|
+
Auto-detection is ideal for:
|
|
232
|
+
|
|
233
|
+
* **Development environments**: Different developers may have different hardware
|
|
234
|
+
* **Portable code**: Applications that run on various machines
|
|
235
|
+
* **Default optimization**: When you want optimal performance without tuning
|
|
236
|
+
* **CI/CD pipelines**: Runners may have varying CPU counts
|
|
237
|
+
* **Container deployments**: CPU allocation may vary by environment
|
|
238
|
+
|
|
239
|
+
Example use cases:
|
|
240
|
+
[source,ruby]
|
|
241
|
+
----
|
|
242
|
+
# Development script - works well on any machine
|
|
243
|
+
supervisor = Fractor::Supervisor.new(
|
|
244
|
+
worker_pools: [{ worker_class: DataProcessor }]
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# CI pipeline - adapts to runner hardware
|
|
248
|
+
supervisor = Fractor::Supervisor.new(
|
|
249
|
+
worker_pools: [{ worker_class: TestRunner }]
|
|
250
|
+
)
|
|
251
|
+
----
|
|
252
|
+
|
|
253
|
+
=== When to Use Explicit Configuration
|
|
254
|
+
|
|
255
|
+
Explicit configuration is better for:
|
|
256
|
+
|
|
257
|
+
* **Production systems**: Predictable resource usage for capacity planning
|
|
258
|
+
* **Resource-constrained environments**: Limit workers to avoid overwhelming system
|
|
259
|
+
* **Memory-intensive tasks**: Fewer workers to prevent memory exhaustion
|
|
260
|
+
* **I/O-bound tasks**: More workers than CPUs for better throughput
|
|
261
|
+
* **Mixed workloads**: Different worker counts for different task types
|
|
262
|
+
|
|
263
|
+
Example use cases:
|
|
264
|
+
[source,ruby]
|
|
265
|
+
----
|
|
266
|
+
# Memory-intensive processing - limit workers
|
|
267
|
+
supervisor = Fractor::Supervisor.new(
|
|
268
|
+
worker_pools: [
|
|
269
|
+
{ worker_class: MemoryHeavyWorker, num_workers: 2 }
|
|
270
|
+
]
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# I/O-bound tasks - more workers than CPUs
|
|
274
|
+
num_io_workers = Etc.nprocessors * 2 # <1>
|
|
275
|
+
supervisor = Fractor::Supervisor.new(
|
|
276
|
+
worker_pools: [
|
|
277
|
+
{ worker_class: ApiWorker, num_workers: num_io_workers }
|
|
278
|
+
]
|
|
279
|
+
)
|
|
280
|
+
----
|
|
281
|
+
<1> I/O-bound tasks benefit from more workers
|
|
282
|
+
|
|
283
|
+
=== Auto-Detection Mechanism
|
|
284
|
+
|
|
285
|
+
Fractor uses Ruby's `Etc.nprocessors`:
|
|
286
|
+
|
|
287
|
+
[source,ruby]
|
|
288
|
+
----
|
|
289
|
+
require 'etc'
|
|
290
|
+
|
|
291
|
+
# Returns number of available processors
|
|
292
|
+
# - Physical cores on bare metal
|
|
293
|
+
# - Virtual CPUs in VMs
|
|
294
|
+
# - CPU quota in containers (if set)
|
|
295
|
+
num_processors = Etc.nprocessors
|
|
296
|
+
----
|
|
297
|
+
|
|
298
|
+
Behavior in different environments:
|
|
299
|
+
|
|
300
|
+
* **Bare metal**: Returns physical CPU cores
|
|
301
|
+
* **Virtual machines**: Returns allocated vCPUs
|
|
302
|
+
* **Docker containers**: Returns container CPU quota (if set) or host CPUs
|
|
303
|
+
* **Kubernetes pods**: Returns CPU limit (if set) or node CPUs
|
|
304
|
+
|
|
305
|
+
=== Performance Considerations
|
|
306
|
+
|
|
307
|
+
==== CPU-Bound Tasks
|
|
308
|
+
|
|
309
|
+
For CPU-intensive work, auto-detection provides optimal parallelism:
|
|
310
|
+
|
|
311
|
+
[source,ruby]
|
|
312
|
+
----
|
|
313
|
+
# Good: One worker per CPU core
|
|
314
|
+
supervisor = Fractor::Supervisor.new(
|
|
315
|
+
worker_pools: [{ worker_class: CpuIntensiveWorker }]
|
|
316
|
+
)
|
|
317
|
+
----
|
|
318
|
+
|
|
319
|
+
==== I/O-Bound Tasks
|
|
320
|
+
|
|
321
|
+
For I/O-heavy work, consider explicit over-subscription:
|
|
322
|
+
|
|
323
|
+
[source,ruby]
|
|
324
|
+
----
|
|
325
|
+
# Better: More workers than CPUs for I/O tasks
|
|
326
|
+
io_workers = Etc.nprocessors * 2
|
|
327
|
+
supervisor = Fractor::Supervisor.new(
|
|
328
|
+
worker_pools: [
|
|
329
|
+
{ worker_class: ApiClient, num_workers: io_workers }
|
|
330
|
+
]
|
|
331
|
+
)
|
|
332
|
+
----
|
|
333
|
+
|
|
334
|
+
==== Memory-Intensive Tasks
|
|
335
|
+
|
|
336
|
+
For memory-heavy work, limit workers to prevent exhaustion:
|
|
337
|
+
|
|
338
|
+
[source,ruby]
|
|
339
|
+
----
|
|
340
|
+
# Better: Fewer workers for memory-intensive tasks
|
|
341
|
+
max_workers = [Etc.nprocessors / 2, 2].max # <1>
|
|
342
|
+
supervisor = Fractor::Supervisor.new(
|
|
343
|
+
worker_pools: [
|
|
344
|
+
{ worker_class: LargeDataProcessor, num_workers: max_workers }
|
|
345
|
+
]
|
|
346
|
+
)
|
|
347
|
+
----
|
|
348
|
+
<1> Use half the CPUs, minimum of 2
|
|
349
|
+
|
|
350
|
+
=== Mixed Configuration Strategy
|
|
351
|
+
|
|
352
|
+
Combine auto-detection with explicit limits:
|
|
353
|
+
|
|
354
|
+
[source,ruby]
|
|
355
|
+
----
|
|
356
|
+
supervisor = Fractor::Supervisor.new(
|
|
357
|
+
worker_pools: [
|
|
358
|
+
# Light CPU work - auto-detect
|
|
359
|
+
{ worker_class: FastWorker },
|
|
360
|
+
|
|
361
|
+
# Heavy memory work - limit to 2
|
|
362
|
+
{ worker_class: MemoryHeavyWorker, num_workers: 2 },
|
|
363
|
+
|
|
364
|
+
# I/O work - 2x CPUs
|
|
365
|
+
{ worker_class: IoWorker, num_workers: Etc.nprocessors * 2 }
|
|
366
|
+
]
|
|
367
|
+
)
|
|
368
|
+
----
|
|
369
|
+
|
|
370
|
+
== Best Practices
|
|
371
|
+
|
|
372
|
+
=== Development
|
|
373
|
+
|
|
374
|
+
Use auto-detection for development:
|
|
375
|
+
|
|
376
|
+
[source,ruby]
|
|
377
|
+
----
|
|
378
|
+
# Good for development - adapts to developer's machine
|
|
379
|
+
supervisor = Fractor::Supervisor.new(
|
|
380
|
+
worker_pools: [{ worker_class: MyWorker }]
|
|
381
|
+
)
|
|
382
|
+
----
|
|
383
|
+
|
|
384
|
+
=== Production
|
|
385
|
+
|
|
386
|
+
Start with auto-detection, then tune based on monitoring:
|
|
387
|
+
|
|
388
|
+
[source,ruby]
|
|
389
|
+
----
|
|
390
|
+
# Production approach
|
|
391
|
+
num_workers = ENV.fetch('NUM_WORKERS', Etc.nprocessors).to_i
|
|
392
|
+
|
|
393
|
+
supervisor = Fractor::Supervisor.new(
|
|
394
|
+
worker_pools: [
|
|
395
|
+
{ worker_class: MyWorker, num_workers: num_workers }
|
|
396
|
+
]
|
|
397
|
+
)
|
|
398
|
+
----
|
|
399
|
+
|
|
400
|
+
This allows:
|
|
401
|
+
- Auto-detection by default
|
|
402
|
+
- Environment-based override for production tuning
|
|
403
|
+
- Easy testing of different worker counts
|
|
404
|
+
|
|
405
|
+
=== Container Environments
|
|
406
|
+
|
|
407
|
+
In containers, consider CPU quotas:
|
|
408
|
+
|
|
409
|
+
[source,ruby]
|
|
410
|
+
----
|
|
411
|
+
# Respect container CPU limits
|
|
412
|
+
def optimal_workers
|
|
413
|
+
# Auto-detection respects container quotas
|
|
414
|
+
Etc.nprocessors
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
supervisor = Fractor::Supervisor.new(
|
|
418
|
+
worker_pools: [
|
|
419
|
+
{ worker_class: MyWorker, num_workers: optimal_workers }
|
|
420
|
+
]
|
|
421
|
+
)
|
|
422
|
+
----
|
|
38
423
|
|
|
39
|
-
|
|
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
|
|
424
|
+
== Next Steps
|
|
46
425
|
|
|
47
|
-
|
|
426
|
+
After understanding auto-detection, explore:
|
|
48
427
|
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* Best practice: use auto-detection for development, tune for production if needed
|
|
428
|
+
* link:../simple/README.adoc[Simple Example] - Basic Fractor concepts
|
|
429
|
+
* link:../specialized_workers/README.adoc[Specialized Workers] - Multiple worker pools with different configurations
|
|
430
|
+
* link:../multi_work_type/README.adoc[Multi Work Type] - Handling different work types efficiently
|
|
@@ -86,8 +86,8 @@ puts
|
|
|
86
86
|
|
|
87
87
|
supervisor1 = Fractor::Supervisor.new(
|
|
88
88
|
worker_pools: [
|
|
89
|
-
{ worker_class: ComputeWorker } # No num_workers specified
|
|
90
|
-
]
|
|
89
|
+
{ worker_class: ComputeWorker }, # No num_workers specified
|
|
90
|
+
],
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
# Add work items
|
|
@@ -97,7 +97,7 @@ supervisor1.add_work_items(work_items)
|
|
|
97
97
|
puts "Processing 10 work items with auto-detected workers..."
|
|
98
98
|
supervisor1.run
|
|
99
99
|
|
|
100
|
-
puts "Results: #{supervisor1.results.results.map(&:result).sort.join(
|
|
100
|
+
puts "Results: #{supervisor1.results.results.map(&:result).sort.join(', ')}"
|
|
101
101
|
puts "✓ Auto-detection successful!"
|
|
102
102
|
puts
|
|
103
103
|
|
|
@@ -110,8 +110,8 @@ puts
|
|
|
110
110
|
|
|
111
111
|
supervisor2 = Fractor::Supervisor.new(
|
|
112
112
|
worker_pools: [
|
|
113
|
-
{ worker_class: ComputeWorker, num_workers: 4 }
|
|
114
|
-
]
|
|
113
|
+
{ worker_class: ComputeWorker, num_workers: 4 },
|
|
114
|
+
],
|
|
115
115
|
)
|
|
116
116
|
|
|
117
117
|
supervisor2.add_work_items((11..20).map { |i| ComputeWork.new(i) })
|
|
@@ -119,7 +119,7 @@ supervisor2.add_work_items((11..20).map { |i| ComputeWork.new(i) })
|
|
|
119
119
|
puts "Processing 10 work items with 4 explicitly configured workers..."
|
|
120
120
|
supervisor2.run
|
|
121
121
|
|
|
122
|
-
puts "Results: #{supervisor2.results.results.map(&:result).sort.join(
|
|
122
|
+
puts "Results: #{supervisor2.results.results.map(&:result).sort.join(', ')}"
|
|
123
123
|
puts "✓ Explicit configuration successful!"
|
|
124
124
|
puts
|
|
125
125
|
|
|
@@ -135,8 +135,8 @@ puts
|
|
|
135
135
|
supervisor3 = Fractor::Supervisor.new(
|
|
136
136
|
worker_pools: [
|
|
137
137
|
{ worker_class: ComputeWorker }, # Auto-detected
|
|
138
|
-
{ worker_class: ComputeWorker, num_workers: 2 } # Explicit
|
|
139
|
-
]
|
|
138
|
+
{ worker_class: ComputeWorker, num_workers: 2 }, # Explicit
|
|
139
|
+
],
|
|
140
140
|
)
|
|
141
141
|
|
|
142
142
|
supervisor3.add_work_items((21..30).map { |i| ComputeWork.new(i) })
|
|
@@ -144,7 +144,7 @@ supervisor3.add_work_items((21..30).map { |i| ComputeWork.new(i) })
|
|
|
144
144
|
puts "Processing 10 work items with mixed configuration..."
|
|
145
145
|
supervisor3.run
|
|
146
146
|
|
|
147
|
-
puts "Results: #{supervisor3.results.results.map(&:result).sort.join(
|
|
147
|
+
puts "Results: #{supervisor3.results.results.map(&:result).sort.join(', ')}"
|
|
148
148
|
puts "✓ Mixed configuration successful!"
|
|
149
149
|
puts
|
|
150
150
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module ContinuousChat
|
|
8
|
+
# Message packet class for handling protocol messages
|
|
9
|
+
class MessagePacket
|
|
10
|
+
attr_reader :type, :data, :timestamp
|
|
11
|
+
|
|
12
|
+
def initialize(type, data, timestamp = Time.now.to_i)
|
|
13
|
+
@type = type.to_sym
|
|
14
|
+
@data = data
|
|
15
|
+
@timestamp = timestamp
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Convert to JSON string
|
|
19
|
+
def to_json(*_args)
|
|
20
|
+
{
|
|
21
|
+
type: @type,
|
|
22
|
+
data: @data,
|
|
23
|
+
timestamp: @timestamp,
|
|
24
|
+
}.to_json
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# String representation
|
|
28
|
+
def to_s
|
|
29
|
+
to_json
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Helper module for message protocol
|
|
34
|
+
module MessageProtocol
|
|
35
|
+
# Create a packet of the given type with data
|
|
36
|
+
def self.create_packet(type, data)
|
|
37
|
+
MessagePacket.new(type, data).to_json
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Parse a JSON string into a message packet
|
|
41
|
+
def self.parse_packet(json_string)
|
|
42
|
+
data = JSON.parse(json_string)
|
|
43
|
+
type = data["type"]&.to_sym
|
|
44
|
+
content = data["data"] || {}
|
|
45
|
+
timestamp = data["timestamp"] || Time.now.to_i
|
|
46
|
+
|
|
47
|
+
MessagePacket.new(type, content, timestamp)
|
|
48
|
+
rescue JSON::ParserError => e
|
|
49
|
+
puts "Error parsing message: #{e.message}"
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|