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.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-main-ci-rubocop-yml +552 -0
  3. data/.rubocop.yml +14 -8
  4. data/.rubocop_todo.yml +284 -43
  5. data/README.adoc +111 -950
  6. data/docs/.lycheeignore +16 -0
  7. data/docs/Gemfile +24 -0
  8. data/docs/README.md +157 -0
  9. data/docs/_config.yml +151 -0
  10. data/docs/_features/error-handling.adoc +1192 -0
  11. data/docs/_features/index.adoc +80 -0
  12. data/docs/_features/monitoring.adoc +589 -0
  13. data/docs/_features/signal-handling.adoc +202 -0
  14. data/docs/_features/workflows.adoc +1235 -0
  15. data/docs/_guides/continuous-mode.adoc +736 -0
  16. data/docs/_guides/cookbook.adoc +1133 -0
  17. data/docs/_guides/index.adoc +55 -0
  18. data/docs/_guides/pipeline-mode.adoc +730 -0
  19. data/docs/_guides/troubleshooting.adoc +358 -0
  20. data/docs/_pages/architecture.adoc +1390 -0
  21. data/docs/_pages/core-concepts.adoc +1392 -0
  22. data/docs/_pages/design-principles.adoc +862 -0
  23. data/docs/_pages/getting-started.adoc +290 -0
  24. data/docs/_pages/installation.adoc +143 -0
  25. data/docs/_reference/api.adoc +1080 -0
  26. data/docs/_reference/error-reporting.adoc +670 -0
  27. data/docs/_reference/examples.adoc +181 -0
  28. data/docs/_reference/index.adoc +96 -0
  29. data/docs/_reference/troubleshooting.adoc +862 -0
  30. data/docs/_tutorials/complex-workflows.adoc +1022 -0
  31. data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
  32. data/docs/_tutorials/first-application.adoc +384 -0
  33. data/docs/_tutorials/index.adoc +48 -0
  34. data/docs/_tutorials/long-running-services.adoc +931 -0
  35. data/docs/assets/images/favicon-16.png +0 -0
  36. data/docs/assets/images/favicon-32.png +0 -0
  37. data/docs/assets/images/favicon-48.png +0 -0
  38. data/docs/assets/images/favicon.ico +0 -0
  39. data/docs/assets/images/favicon.png +0 -0
  40. data/docs/assets/images/favicon.svg +45 -0
  41. data/docs/assets/images/fractor-icon.svg +49 -0
  42. data/docs/assets/images/fractor-logo.svg +61 -0
  43. data/docs/index.adoc +131 -0
  44. data/docs/lychee.toml +39 -0
  45. data/examples/api_aggregator/README.adoc +627 -0
  46. data/examples/api_aggregator/api_aggregator.rb +376 -0
  47. data/examples/auto_detection/README.adoc +407 -29
  48. data/examples/auto_detection/auto_detection.rb +9 -9
  49. data/examples/continuous_chat_common/message_protocol.rb +53 -0
  50. data/examples/continuous_chat_fractor/README.adoc +217 -0
  51. data/examples/continuous_chat_fractor/chat_client.rb +303 -0
  52. data/examples/continuous_chat_fractor/chat_common.rb +83 -0
  53. data/examples/continuous_chat_fractor/chat_server.rb +167 -0
  54. data/examples/continuous_chat_fractor/simulate.rb +345 -0
  55. data/examples/continuous_chat_server/README.adoc +135 -0
  56. data/examples/continuous_chat_server/chat_client.rb +303 -0
  57. data/examples/continuous_chat_server/chat_server.rb +359 -0
  58. data/examples/continuous_chat_server/simulate.rb +343 -0
  59. data/examples/error_reporting.rb +207 -0
  60. data/examples/file_processor/README.adoc +170 -0
  61. data/examples/file_processor/file_processor.rb +615 -0
  62. data/examples/file_processor/sample_files/invalid.csv +1 -0
  63. data/examples/file_processor/sample_files/orders.xml +24 -0
  64. data/examples/file_processor/sample_files/products.json +23 -0
  65. data/examples/file_processor/sample_files/users.csv +6 -0
  66. data/examples/hierarchical_hasher/README.adoc +629 -41
  67. data/examples/hierarchical_hasher/hierarchical_hasher.rb +12 -8
  68. data/examples/image_processor/README.adoc +610 -0
  69. data/examples/image_processor/image_processor.rb +349 -0
  70. data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
  71. data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
  72. data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
  73. data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
  74. data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
  75. data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
  76. data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
  77. data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
  78. data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
  79. data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
  80. data/examples/image_processor/test_images/sample_1.png +1 -0
  81. data/examples/image_processor/test_images/sample_10.png +1 -0
  82. data/examples/image_processor/test_images/sample_2.png +1 -0
  83. data/examples/image_processor/test_images/sample_3.png +1 -0
  84. data/examples/image_processor/test_images/sample_4.png +1 -0
  85. data/examples/image_processor/test_images/sample_5.png +1 -0
  86. data/examples/image_processor/test_images/sample_6.png +1 -0
  87. data/examples/image_processor/test_images/sample_7.png +1 -0
  88. data/examples/image_processor/test_images/sample_8.png +1 -0
  89. data/examples/image_processor/test_images/sample_9.png +1 -0
  90. data/examples/log_analyzer/README.adoc +662 -0
  91. data/examples/log_analyzer/log_analyzer.rb +579 -0
  92. data/examples/log_analyzer/sample_logs/apache.log +20 -0
  93. data/examples/log_analyzer/sample_logs/json.log +15 -0
  94. data/examples/log_analyzer/sample_logs/nginx.log +15 -0
  95. data/examples/log_analyzer/sample_logs/rails.log +29 -0
  96. data/examples/multi_work_type/README.adoc +576 -26
  97. data/examples/multi_work_type/multi_work_type.rb +30 -29
  98. data/examples/performance_monitoring.rb +120 -0
  99. data/examples/pipeline_processing/README.adoc +740 -26
  100. data/examples/pipeline_processing/pipeline_processing.rb +16 -16
  101. data/examples/priority_work_example.rb +155 -0
  102. data/examples/producer_subscriber/README.adoc +889 -46
  103. data/examples/producer_subscriber/producer_subscriber.rb +20 -16
  104. data/examples/scatter_gather/README.adoc +829 -27
  105. data/examples/scatter_gather/scatter_gather.rb +29 -28
  106. data/examples/simple/README.adoc +347 -0
  107. data/examples/simple/sample.rb +5 -5
  108. data/examples/specialized_workers/README.adoc +622 -26
  109. data/examples/specialized_workers/specialized_workers.rb +88 -45
  110. data/examples/stream_processor/README.adoc +206 -0
  111. data/examples/stream_processor/stream_processor.rb +284 -0
  112. data/examples/web_scraper/README.adoc +625 -0
  113. data/examples/web_scraper/web_scraper.rb +285 -0
  114. data/examples/workflow/README.adoc +406 -0
  115. data/examples/workflow/circuit_breaker/README.adoc +360 -0
  116. data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
  117. data/examples/workflow/conditional/README.adoc +483 -0
  118. data/examples/workflow/conditional/conditional_workflow.rb +215 -0
  119. data/examples/workflow/dead_letter_queue/README.adoc +374 -0
  120. data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
  121. data/examples/workflow/fan_out/README.adoc +381 -0
  122. data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
  123. data/examples/workflow/retry/README.adoc +248 -0
  124. data/examples/workflow/retry/retry_workflow.rb +195 -0
  125. data/examples/workflow/simple_linear/README.adoc +267 -0
  126. data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
  127. data/examples/workflow/simplified/README.adoc +329 -0
  128. data/examples/workflow/simplified/simplified_workflow.rb +222 -0
  129. data/exe/fractor +10 -0
  130. data/lib/fractor/cli.rb +288 -0
  131. data/lib/fractor/configuration.rb +307 -0
  132. data/lib/fractor/continuous_server.rb +183 -0
  133. data/lib/fractor/error_formatter.rb +72 -0
  134. data/lib/fractor/error_report_generator.rb +152 -0
  135. data/lib/fractor/error_reporter.rb +244 -0
  136. data/lib/fractor/error_statistics.rb +147 -0
  137. data/lib/fractor/execution_tracer.rb +162 -0
  138. data/lib/fractor/logger.rb +230 -0
  139. data/lib/fractor/main_loop_handler.rb +406 -0
  140. data/lib/fractor/main_loop_handler3.rb +135 -0
  141. data/lib/fractor/main_loop_handler4.rb +299 -0
  142. data/lib/fractor/performance_metrics_collector.rb +181 -0
  143. data/lib/fractor/performance_monitor.rb +215 -0
  144. data/lib/fractor/performance_report_generator.rb +202 -0
  145. data/lib/fractor/priority_work.rb +93 -0
  146. data/lib/fractor/priority_work_queue.rb +189 -0
  147. data/lib/fractor/result_aggregator.rb +33 -1
  148. data/lib/fractor/shutdown_handler.rb +168 -0
  149. data/lib/fractor/signal_handler.rb +80 -0
  150. data/lib/fractor/supervisor.rb +430 -144
  151. data/lib/fractor/supervisor_logger.rb +88 -0
  152. data/lib/fractor/version.rb +1 -1
  153. data/lib/fractor/work.rb +12 -0
  154. data/lib/fractor/work_distribution_manager.rb +151 -0
  155. data/lib/fractor/work_queue.rb +88 -0
  156. data/lib/fractor/work_result.rb +181 -9
  157. data/lib/fractor/worker.rb +75 -1
  158. data/lib/fractor/workflow/builder.rb +210 -0
  159. data/lib/fractor/workflow/chain_builder.rb +169 -0
  160. data/lib/fractor/workflow/circuit_breaker.rb +183 -0
  161. data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
  162. data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
  163. data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
  164. data/lib/fractor/workflow/execution_hooks.rb +39 -0
  165. data/lib/fractor/workflow/execution_strategy.rb +225 -0
  166. data/lib/fractor/workflow/execution_trace.rb +134 -0
  167. data/lib/fractor/workflow/helpers.rb +191 -0
  168. data/lib/fractor/workflow/job.rb +290 -0
  169. data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
  170. data/lib/fractor/workflow/logger.rb +110 -0
  171. data/lib/fractor/workflow/pre_execution_context.rb +193 -0
  172. data/lib/fractor/workflow/retry_config.rb +156 -0
  173. data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
  174. data/lib/fractor/workflow/retry_strategy.rb +93 -0
  175. data/lib/fractor/workflow/structured_logger.rb +30 -0
  176. data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
  177. data/lib/fractor/workflow/visualizer.rb +211 -0
  178. data/lib/fractor/workflow/workflow_context.rb +132 -0
  179. data/lib/fractor/workflow/workflow_executor.rb +669 -0
  180. data/lib/fractor/workflow/workflow_result.rb +55 -0
  181. data/lib/fractor/workflow/workflow_validator.rb +295 -0
  182. data/lib/fractor/workflow.rb +333 -0
  183. data/lib/fractor/wrapped_ractor.rb +66 -91
  184. data/lib/fractor/wrapped_ractor3.rb +161 -0
  185. data/lib/fractor/wrapped_ractor4.rb +242 -0
  186. data/lib/fractor.rb +93 -3
  187. metadata +192 -6
  188. data/tests/sample.rb.bak +0 -309
  189. 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
- Shows how Fractor can automatically detect the number of available processors on your system and create the optimal number of workers without manual configuration.
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
- == What This Demonstrates
9
+ This example focuses on demonstrating:
10
10
 
11
- * How Fractor automatically detects the number of available processors
11
+ * Automatic detection of available processors using `Etc.nprocessors`
12
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
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
- == When to Use Auto-Detection
63
+ Auto-Detection:
64
+ 4 workers created 16 workers created
65
+ 100% CPU utilization 100% CPU utilization
66
+ ✓ Optimal ✓ Optimal
17
67
 
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
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
- == When to Set Explicit Values
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
- * 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
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
- == Running the Example
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
- The script will:
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
- 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
424
+ == Next Steps
46
425
 
47
- == Key Takeaways
426
+ After understanding auto-detection, explore:
48
427
 
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
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