fractor 0.1.6 → 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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +227 -102
  3. data/README.adoc +113 -1940
  4. data/docs/.lycheeignore +16 -0
  5. data/docs/Gemfile +24 -0
  6. data/docs/README.md +157 -0
  7. data/docs/_config.yml +151 -0
  8. data/docs/_features/error-handling.adoc +1192 -0
  9. data/docs/_features/index.adoc +80 -0
  10. data/docs/_features/monitoring.adoc +589 -0
  11. data/docs/_features/signal-handling.adoc +202 -0
  12. data/docs/_features/workflows.adoc +1235 -0
  13. data/docs/_guides/continuous-mode.adoc +736 -0
  14. data/docs/_guides/cookbook.adoc +1133 -0
  15. data/docs/_guides/index.adoc +55 -0
  16. data/docs/_guides/pipeline-mode.adoc +730 -0
  17. data/docs/_guides/troubleshooting.adoc +358 -0
  18. data/docs/_pages/architecture.adoc +1390 -0
  19. data/docs/_pages/core-concepts.adoc +1392 -0
  20. data/docs/_pages/design-principles.adoc +862 -0
  21. data/docs/_pages/getting-started.adoc +290 -0
  22. data/docs/_pages/installation.adoc +143 -0
  23. data/docs/_reference/api.adoc +1080 -0
  24. data/docs/_reference/error-reporting.adoc +670 -0
  25. data/docs/_reference/examples.adoc +181 -0
  26. data/docs/_reference/index.adoc +96 -0
  27. data/docs/_reference/troubleshooting.adoc +862 -0
  28. data/docs/_tutorials/complex-workflows.adoc +1022 -0
  29. data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
  30. data/docs/_tutorials/first-application.adoc +384 -0
  31. data/docs/_tutorials/index.adoc +48 -0
  32. data/docs/_tutorials/long-running-services.adoc +931 -0
  33. data/docs/assets/images/favicon-16.png +0 -0
  34. data/docs/assets/images/favicon-32.png +0 -0
  35. data/docs/assets/images/favicon-48.png +0 -0
  36. data/docs/assets/images/favicon.ico +0 -0
  37. data/docs/assets/images/favicon.png +0 -0
  38. data/docs/assets/images/favicon.svg +45 -0
  39. data/docs/assets/images/fractor-icon.svg +49 -0
  40. data/docs/assets/images/fractor-logo.svg +61 -0
  41. data/docs/index.adoc +131 -0
  42. data/docs/lychee.toml +39 -0
  43. data/examples/api_aggregator/README.adoc +627 -0
  44. data/examples/api_aggregator/api_aggregator.rb +376 -0
  45. data/examples/auto_detection/README.adoc +407 -29
  46. data/examples/continuous_chat_common/message_protocol.rb +1 -1
  47. data/examples/error_reporting.rb +207 -0
  48. data/examples/file_processor/README.adoc +170 -0
  49. data/examples/file_processor/file_processor.rb +615 -0
  50. data/examples/file_processor/sample_files/invalid.csv +1 -0
  51. data/examples/file_processor/sample_files/orders.xml +24 -0
  52. data/examples/file_processor/sample_files/products.json +23 -0
  53. data/examples/file_processor/sample_files/users.csv +6 -0
  54. data/examples/hierarchical_hasher/README.adoc +629 -41
  55. data/examples/image_processor/README.adoc +610 -0
  56. data/examples/image_processor/image_processor.rb +349 -0
  57. data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
  58. data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
  59. data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
  60. data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
  61. data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
  62. data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
  63. data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
  64. data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
  65. data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
  66. data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
  67. data/examples/image_processor/test_images/sample_1.png +1 -0
  68. data/examples/image_processor/test_images/sample_10.png +1 -0
  69. data/examples/image_processor/test_images/sample_2.png +1 -0
  70. data/examples/image_processor/test_images/sample_3.png +1 -0
  71. data/examples/image_processor/test_images/sample_4.png +1 -0
  72. data/examples/image_processor/test_images/sample_5.png +1 -0
  73. data/examples/image_processor/test_images/sample_6.png +1 -0
  74. data/examples/image_processor/test_images/sample_7.png +1 -0
  75. data/examples/image_processor/test_images/sample_8.png +1 -0
  76. data/examples/image_processor/test_images/sample_9.png +1 -0
  77. data/examples/log_analyzer/README.adoc +662 -0
  78. data/examples/log_analyzer/log_analyzer.rb +579 -0
  79. data/examples/log_analyzer/sample_logs/apache.log +20 -0
  80. data/examples/log_analyzer/sample_logs/json.log +15 -0
  81. data/examples/log_analyzer/sample_logs/nginx.log +15 -0
  82. data/examples/log_analyzer/sample_logs/rails.log +29 -0
  83. data/examples/multi_work_type/README.adoc +576 -26
  84. data/examples/performance_monitoring.rb +120 -0
  85. data/examples/pipeline_processing/README.adoc +740 -26
  86. data/examples/pipeline_processing/pipeline_processing.rb +2 -2
  87. data/examples/priority_work_example.rb +155 -0
  88. data/examples/producer_subscriber/README.adoc +889 -46
  89. data/examples/scatter_gather/README.adoc +829 -27
  90. data/examples/simple/README.adoc +347 -0
  91. data/examples/specialized_workers/README.adoc +622 -26
  92. data/examples/specialized_workers/specialized_workers.rb +44 -8
  93. data/examples/stream_processor/README.adoc +206 -0
  94. data/examples/stream_processor/stream_processor.rb +284 -0
  95. data/examples/web_scraper/README.adoc +625 -0
  96. data/examples/web_scraper/web_scraper.rb +285 -0
  97. data/examples/workflow/README.adoc +406 -0
  98. data/examples/workflow/circuit_breaker/README.adoc +360 -0
  99. data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
  100. data/examples/workflow/conditional/README.adoc +483 -0
  101. data/examples/workflow/conditional/conditional_workflow.rb +215 -0
  102. data/examples/workflow/dead_letter_queue/README.adoc +374 -0
  103. data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
  104. data/examples/workflow/fan_out/README.adoc +381 -0
  105. data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
  106. data/examples/workflow/retry/README.adoc +248 -0
  107. data/examples/workflow/retry/retry_workflow.rb +195 -0
  108. data/examples/workflow/simple_linear/README.adoc +267 -0
  109. data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
  110. data/examples/workflow/simplified/README.adoc +329 -0
  111. data/examples/workflow/simplified/simplified_workflow.rb +222 -0
  112. data/exe/fractor +10 -0
  113. data/lib/fractor/cli.rb +288 -0
  114. data/lib/fractor/configuration.rb +307 -0
  115. data/lib/fractor/continuous_server.rb +60 -65
  116. data/lib/fractor/error_formatter.rb +72 -0
  117. data/lib/fractor/error_report_generator.rb +152 -0
  118. data/lib/fractor/error_reporter.rb +244 -0
  119. data/lib/fractor/error_statistics.rb +147 -0
  120. data/lib/fractor/execution_tracer.rb +162 -0
  121. data/lib/fractor/logger.rb +230 -0
  122. data/lib/fractor/main_loop_handler.rb +406 -0
  123. data/lib/fractor/main_loop_handler3.rb +135 -0
  124. data/lib/fractor/main_loop_handler4.rb +299 -0
  125. data/lib/fractor/performance_metrics_collector.rb +181 -0
  126. data/lib/fractor/performance_monitor.rb +215 -0
  127. data/lib/fractor/performance_report_generator.rb +202 -0
  128. data/lib/fractor/priority_work.rb +93 -0
  129. data/lib/fractor/priority_work_queue.rb +189 -0
  130. data/lib/fractor/result_aggregator.rb +32 -0
  131. data/lib/fractor/shutdown_handler.rb +168 -0
  132. data/lib/fractor/signal_handler.rb +80 -0
  133. data/lib/fractor/supervisor.rb +382 -269
  134. data/lib/fractor/supervisor_logger.rb +88 -0
  135. data/lib/fractor/version.rb +1 -1
  136. data/lib/fractor/work.rb +12 -0
  137. data/lib/fractor/work_distribution_manager.rb +151 -0
  138. data/lib/fractor/work_queue.rb +20 -0
  139. data/lib/fractor/work_result.rb +181 -9
  140. data/lib/fractor/worker.rb +73 -0
  141. data/lib/fractor/workflow/builder.rb +210 -0
  142. data/lib/fractor/workflow/chain_builder.rb +169 -0
  143. data/lib/fractor/workflow/circuit_breaker.rb +183 -0
  144. data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
  145. data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
  146. data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
  147. data/lib/fractor/workflow/execution_hooks.rb +39 -0
  148. data/lib/fractor/workflow/execution_strategy.rb +225 -0
  149. data/lib/fractor/workflow/execution_trace.rb +134 -0
  150. data/lib/fractor/workflow/helpers.rb +191 -0
  151. data/lib/fractor/workflow/job.rb +290 -0
  152. data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
  153. data/lib/fractor/workflow/logger.rb +110 -0
  154. data/lib/fractor/workflow/pre_execution_context.rb +193 -0
  155. data/lib/fractor/workflow/retry_config.rb +156 -0
  156. data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
  157. data/lib/fractor/workflow/retry_strategy.rb +93 -0
  158. data/lib/fractor/workflow/structured_logger.rb +30 -0
  159. data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
  160. data/lib/fractor/workflow/visualizer.rb +211 -0
  161. data/lib/fractor/workflow/workflow_context.rb +132 -0
  162. data/lib/fractor/workflow/workflow_executor.rb +669 -0
  163. data/lib/fractor/workflow/workflow_result.rb +55 -0
  164. data/lib/fractor/workflow/workflow_validator.rb +295 -0
  165. data/lib/fractor/workflow.rb +333 -0
  166. data/lib/fractor/wrapped_ractor.rb +66 -101
  167. data/lib/fractor/wrapped_ractor3.rb +161 -0
  168. data/lib/fractor/wrapped_ractor4.rb +242 -0
  169. data/lib/fractor.rb +92 -4
  170. metadata +179 -6
  171. data/tests/sample.rb.bak +0 -309
  172. data/tests/sample_working.rb.bak +0 -209
@@ -0,0 +1,1390 @@
1
+ ---
2
+ layout: default
3
+ title: Architecture
4
+ nav_order: 3
5
+ ---
6
+ = Architecture guide
7
+
8
+ == Overview
9
+
10
+ This guide provides an in-depth exploration of Fractor's architecture, covering
11
+ system components, concurrency model, workflow execution, queue management, and
12
+ error handling strategies.
13
+
14
+ .Architecture documentation structure
15
+ [source]
16
+ ----
17
+ Architecture Guide
18
+ ├── High-Level Architecture (System components & responsibilities)
19
+ ├── Concurrency Architecture (Ractor-based parallelism)
20
+ ├── Workflow Architecture (Multi-step processing graphs)
21
+ ├── Queue Architecture (Work distribution & priority)
22
+ └── Error Handling Architecture (Error propagation & recovery)
23
+ ----
24
+
25
+ == High-level architecture
26
+
27
+ === System components
28
+
29
+ Fractor consists of four primary architectural layers:
30
+
31
+ [source]
32
+ ----
33
+ ┌──────────────────────────────────────────────────────────────┐
34
+ │ APPLICATION LAYER │
35
+ │ │
36
+ │ Business Logic Workflows Work Definitions │
37
+ │ - Domain code - Job graphs - Work subclasses │
38
+ │ - Processing - Dependencies - Worker subclasses │
39
+ │ - Validation - Type safety - Results │
40
+ └──────────────────────────────────────────────────────────────┘
41
+
42
+
43
+ ┌──────────────────────────────────────────────────────────────┐
44
+ │ ORCHESTRATION LAYER │
45
+ │ │
46
+ │ Supervisor WorkflowExecutor ContinuousServer │
47
+ │ - Pool mgmt - Job execution - Long-running │
48
+ │ - Distribution - Dep resolution - Threading │
49
+ │ - Coordination - State mgmt - Callbacks │
50
+ └──────────────────────────────────────────────────────────────┘
51
+
52
+
53
+ ┌──────────────────────────────────────────────────────────────┐
54
+ │ CONCURRENCY LAYER │
55
+ │ │
56
+ │ WrappedRactor WorkQueue ResultAggregator │
57
+ │ - Ractor lifecycle - Thread-safe - Result collection │
58
+ │ - Messaging - Batch ops - Error separation │
59
+ │ - Error handling - Backpressure - Callbacks │
60
+ └──────────────────────────────────────────────────────────────┘
61
+
62
+
63
+ ┌──────────────────────────────────────────────────────────────┐
64
+ │ RACTOR LAYER │
65
+ │ │
66
+ │ Ractor 1 Ractor 2 Ractor 3 ... Ractor N │
67
+ │ - Worker - Worker - Worker - Worker │
68
+ │ - Isolated - Isolated - Isolated - Isolated │
69
+ │ - Parallel - Parallel - Parallel - Parallel │
70
+ └──────────────────────────────────────────────────────────────┘
71
+ ----
72
+
73
+ === Component responsibilities
74
+
75
+ [cols="2,3,3"]
76
+ |===
77
+ |Layer |Component |Responsibility
78
+
79
+ .4+|Application
80
+ |Business Logic
81
+ |Domain-specific processing and validation
82
+
83
+ |Workflows
84
+ |Define multi-step processing graphs with dependencies
85
+
86
+ |Work Classes
87
+ |Encapsulate input data for processing
88
+
89
+ |Worker Classes
90
+ |Implement processing logic for work items
91
+
92
+ .3+|Orchestration
93
+ |[`Supervisor`](../../lib/fractor/supervisor.rb)
94
+ |Manage worker pools, distribute work, collect results
95
+
96
+ |[`WorkflowExecutor`](../../lib/fractor/workflow/workflow_executor.rb)
97
+ |Execute workflows, resolve dependencies, track state
98
+
99
+ |[`ContinuousServer`](../../lib/fractor/continuous_server.rb)
100
+ |Manage long-running services with threading and callbacks
101
+
102
+ .3+|Concurrency
103
+ |[`WrappedRactor`](../../lib/fractor/wrapped_ractor.rb)
104
+ |Manage Ractor lifecycle, handle messaging
105
+
106
+ |[`WorkQueue`](../../lib/fractor/work_queue.rb) / [`PriorityWorkQueue`](../../lib/fractor/priority_work_queue.rb)
107
+ |Thread-safe work storage with priority support
108
+
109
+ |[`ResultAggregator`](../../lib/fractor/result_aggregator.rb)
110
+ |Collect and organize processing results
111
+
112
+ |Ractor
113
+ |Ruby Ractor
114
+ |Provide true parallelism with memory isolation
115
+ |===
116
+
117
+ === Layered architecture benefits
118
+
119
+ The layered architecture provides several key benefits:
120
+
121
+ . *Separation of concerns*: Each layer has clear, distinct responsibilities
122
+ . *Testability*: Layers can be tested independently
123
+ . *Flexibility*: Swap implementations without affecting other layers
124
+ . *Maintainability*: Changes localized to relevant layers
125
+ . *Extensibility*: Add features by extending appropriate layers
126
+
127
+ === Component lifecycle
128
+
129
+ .Component lifecycle in pipeline mode
130
+ [source]
131
+ ----
132
+ Initialization Phase
133
+
134
+ ├──> Create Supervisor
135
+ │ └──> Configure worker pools
136
+ │ └──> Validate worker classes
137
+
138
+ ├──> Add work items
139
+ │ └──> Validate work objects
140
+ │ └──> Add to queue
141
+
142
+ Execution Phase
143
+
144
+ ├──> Start workers
145
+ │ └──> Create WrappedRactors
146
+ │ └──> Spawn Ractors
147
+ │ └──> Instantiate Workers
148
+
149
+ ├──> Distribution loop
150
+ │ └──> Ractor.select (wait for available worker)
151
+ │ └──> Send work to available Ractor
152
+ │ └──> Worker.process(work)
153
+
154
+ ├──> Collection loop
155
+ │ └──> Receive WorkResult from Ractor
156
+ │ └──> Add to ResultAggregator
157
+ │ └──> Callback execution (if any)
158
+
159
+ Completion Phase
160
+
161
+ └──> All work processed
162
+ └──> Return results to caller
163
+ └──> Cleanup resources
164
+ ----
165
+
166
+ .Component lifecycle in continuous mode
167
+ [source]
168
+ ----
169
+ Initialization Phase
170
+
171
+ ├──> Create Supervisor (continuous: true)
172
+ │ └──> Configure worker pools
173
+
174
+ ├──> Register work sources
175
+ │ └──> Callback functions
176
+
177
+ ├──> Create ContinuousServer (optional)
178
+ │ └──> Setup threading
179
+ │ └──> Results processing thread
180
+
181
+ Execution Phase (Infinite Loop)
182
+
183
+ ├──> Poll work sources
184
+ │ └──> Execute callbacks
185
+ │ └──> Add new work to queue
186
+
187
+ ├──> Timer thread wakes supervisor
188
+ │ └──> Check for idle workers
189
+ │ └──> Distribute available work
190
+
191
+ ├──> Workers process continuously
192
+ │ └──> Return results
193
+ │ └── > Callbacks execute
194
+
195
+ Shutdown Phase
196
+
197
+ ├──> Receive shutdown signal (SIGINT/SIGTERM)
198
+ │ └──> Set @running = false
199
+ │ └──> Complete in-progress work
200
+ │ └──> Send :shutdown to workers
201
+
202
+ └──> Cleanup
203
+ └──> Close Ractors
204
+ └──> Close log files
205
+ └──> Exit cleanly
206
+ ----
207
+
208
+ === Deployment topologies
209
+
210
+ ==== Single-process deployment
211
+
212
+ Simplest deployment for moderate workloads:
213
+
214
+ [source]
215
+ ----
216
+ ┌─────────────────────────────────┐
217
+ │ Application Process │
218
+ │ │
219
+ │ ┌───────────────────────────┐ │
220
+ │ │ Supervisor │ │
221
+ │ │ ┌────────────────────┐ │ │
222
+ │ │ │ Worker Pool (N) │ │ │
223
+ │ │ │ - Ractor 1 │ │ │
224
+ │ │ │ - Ractor 2 │ │ │
225
+ │ │ │ - Ractor N │ │ │
226
+ │ │ └────────────────────┘ │ │
227
+ │ └───────────────────────────┘ │
228
+ └─────────────────────────────────┘
229
+ ----
230
+
231
+ Best for:
232
+
233
+ * Development and testing
234
+ * Small to medium workloads
235
+ * Single machine deployments
236
+ * Memory-constrained environments
237
+
238
+ ==== Multi-pool deployment
239
+
240
+ Multiple worker pools for different work types:
241
+
242
+ [source]
243
+ ----
244
+ ┌─────────────────────────────────────────────────────┐
245
+ │ Application Process │
246
+ │ │
247
+ │ ┌────────────────────────────────────────────┐ │
248
+ │ │ Supervisor │ │
249
+ │ │ ┌──────────────┐ ┌──────────────┐ │ │
250
+ │ │ │ Pool 1 │ │ Pool 2 │ │ │
251
+ │ │ │ CPUWorker │ │ IOWorker │ │ │
252
+ │ │ │ (4 ractors) │ │ (16 ractors) │ │ │
253
+ │ │ └──────────────┘ └──────────────┘ │ │
254
+ │ │ ┌──────────────┐ │ │
255
+ │ │ │ Pool 3 │ │ │
256
+ │ │ │ MixedWorker │ │ │
257
+ │ │ │ (8 ractors) │ │ │
258
+ │ │ └──────────────┘ │ │
259
+ │ └────────────────────────────────────────────┘ │
260
+ └─────────────────────────────────────────────────────┘
261
+ ----
262
+
263
+ Best for:
264
+
265
+ * Mixed CPU/IO workloads
266
+ * Different performance characteristics
267
+ * Resource optimization
268
+ * Specialized processing requirements
269
+
270
+ ==== Distributed queue deployment
271
+
272
+ Multiple processes consuming from shared queue:
273
+
274
+ [source]
275
+ ----
276
+ ┌──────────────────────┐
277
+ │ Work Producer │
278
+ │ (Web app, API) │
279
+ └──────┬───────────────┘
280
+
281
+
282
+ ┌──────────────────────┐
283
+ │ External Queue │
284
+ │ (Redis, RabbitMQ) │
285
+ └──┬────────┬──────┬───┘
286
+ │ │ │
287
+ ▼ ▼ ▼
288
+ ┌────┐ ┌────┐ ┌────┐
289
+ │App1│ │App2│ │AppN│
290
+ │ │ │ │ │ │
291
+ │Sup │ │Sup │ │Sup │
292
+ │ └─┐│ │ └─┐│ │ └─┐│
293
+ │ W │ │ W │ │ W │
294
+ └────┘ └────┘ └────┘
295
+ ----
296
+
297
+ Best for:
298
+
299
+ * High-volume workloads
300
+ * Horizontal scaling
301
+ * Fault tolerance
302
+ * Load balancing
303
+
304
+ == Concurrency architecture
305
+
306
+ === Ractor-based parallelism
307
+
308
+ Fractor leverages Ruby's Ractor feature for true parallelism:
309
+
310
+ [source]
311
+ ----
312
+ Traditional Ruby Threading (GIL-limited)
313
+ ┌──────────────────────────────────────────────┐
314
+ │ Thread 1 Thread 2 Thread 3 Thread 4 │
315
+ │ │ │ │ │ │
316
+ │ └─────────┴─────────┴─────────┘ │
317
+ │ GIL Bottleneck │
318
+ │ (One at a time) │
319
+ └──────────────────────────────────────────────┘
320
+
321
+ Fractor with Ractors (No GIL)
322
+ ┌──────────────────────────────────────────────┐
323
+ │ Ractor1 Ractor2 Ractor3 Ractor4 │
324
+ │ ║ ║ ║ ║ │
325
+ │ ║ ║ ║ ║ │
326
+ │ ▼ ▼ ▼ ▼ │
327
+ │ CPU 1 CPU 2 CPU 3 CPU 4 │
328
+ │ (True parallelism on separate cores) │
329
+ └──────────────────────────────────────────────┘
330
+ ----
331
+
332
+ Key advantages:
333
+
334
+ * *True parallelism*: No GIL limitations
335
+ * *CPU utilization*: All cores utilized simultaneously
336
+ * *Predictable performance*: Linear scaling with cores
337
+ * *Memory isolation*: No race conditions
338
+
339
+ === Message passing protocol
340
+
341
+ Fractor uses structured message passing between components:
342
+
343
+ .Message types and flow
344
+ [source]
345
+ ----
346
+ Supervisor ← → Ractor Communication
347
+
348
+ Messages from Ractor to Supervisor:
349
+ ┌────────────┬──────────────────────────────────┐
350
+ │ Type │ Purpose │
351
+ ├────────────┼──────────────────────────────────┤
352
+ │:initialize │ Ractor ready, request work │
353
+ │:result │ Work completed successfully │
354
+ │:error │ Work failed with error │
355
+ │:shutdown │ Shutdown acknowledged │
356
+ └────────────┴──────────────────────────────────┘
357
+
358
+ Messages from Supervisor to Ractor:
359
+ ┌────────────┬──────────────────────────────────┐
360
+ │ Type │ Purpose │
361
+ ├────────────┼──────────────────────────────────┤
362
+ │Work object │ Work item to process │
363
+ │:shutdown │ Graceful shutdown request │
364
+ └────────────┴──────────────────────────────────┘
365
+ ----
366
+
367
+ .Complete message flow sequence
368
+ [example]
369
+ ====
370
+ [source]
371
+ ----
372
+ Time Supervisor Ractor
373
+ │ │
374
+ ├──── new Ractor(Worker) ───────────────────>│
375
+ │ │
376
+ │ [Initialize]
377
+ │ │
378
+ │<──── :initialize message ─────────────────┤
379
+ │ { type: :initialize, │
380
+ │ processor: "worker-1" } │
381
+ │ │
382
+ ├──── Work object ──────────────────────────>│
383
+ │ │
384
+ │ [Processing]
385
+ │ work.process
386
+ │ │
387
+ │<──── :result message ──────────────────────┤
388
+ │ { type: :result, │
389
+ │ result: WorkResult, │
390
+ │ processor: "worker-1" } │
391
+ │ │
392
+ [Collect result, send next work or mark idle]
393
+ │ │
394
+ ├──── Next Work ────────────────────────────>│
395
+ │ │
396
+ │ [Continue...]
397
+ │ │
398
+ [On shutdown signal] │
399
+ │ │
400
+ ├──── :shutdown ────────────────────────────>│
401
+ │ │
402
+ │ [Cleanup]
403
+ │ │
404
+ │<──── :shutdown ────────────────────────────┤
405
+ │ { type: :shutdown, │
406
+ │ processor: "worker-1" } │
407
+ │ │
408
+ [Remove from active ractors] [Terminate]
409
+ │ │
410
+ ----
411
+ ====
412
+
413
+ === Ruby version compatibility
414
+
415
+ Fractor provides a unified user-facing API while internally adapting to the
416
+ different Ractor communication primitives available in Ruby 3.x and Ruby 4.0+.
417
+
418
+ [cols="2,2,3"]
419
+ |===
420
+ |Ruby Version |Ractor Primitives |Implementation Details
421
+
422
+ |3.0 - 3.x
423
+ |`Ractor.yield` / `Ractor.take`
424
+ |Workers use `Ractor.yield` to send results back. Main ractor uses `Ractor.take` to receive messages from workers.
425
+
426
+ |4.0+
427
+ |`Ractor::Port`
428
+ |Main ractor creates response ports and passes them to workers. Workers send results directly through ports.
429
+ |===
430
+
431
+ .Ruby 3.x implementation (WrappedRactor3)
432
+ [source,ruby]
433
+ ----
434
+ # Worker sends results via Ractor.yield
435
+ class WrappedRactor3 < WrappedRactor
436
+ def start
437
+ @ractor = Ractor.new do
438
+ loop do
439
+ work = Ractor.receive
440
+ result = worker.process(work)
441
+ # Send result back using Ractor.yield
442
+ Ractor.yield({ type: :result, result: result })
443
+ end
444
+ end
445
+ end
446
+
447
+ def receive_message
448
+ # Main ractor receives via Ractor.take
449
+ @ractor&.take
450
+ end
451
+ end
452
+ ----
453
+
454
+ .Ruby 4.0+ implementation (WrappedRactor4)
455
+ [source,ruby]
456
+ ----
457
+ # Worker sends results via response port
458
+ class WrappedRactor4 < WrappedRactor
459
+ attr_reader :response_port
460
+
461
+ def initialize(name, worker_class, response_port: nil)
462
+ super(name, worker_class)
463
+ @response_port = response_port # Port created by main ractor
464
+ end
465
+
466
+ def start
467
+ @ractor = Ractor.new do
468
+ loop do
469
+ # Receive [work, response_port] from main
470
+ work, response_port = Ractor.receive
471
+ result = worker.process(work)
472
+ # Send result through the provided port
473
+ response_port << { type: :result, result: result }
474
+ end
475
+ end
476
+ end
477
+
478
+ def send(work)
479
+ # Main sends [work, response_port] to worker
480
+ @ractor.send([work, @response_port])
481
+ end
482
+ end
483
+ ----
484
+
485
+ .Implications for users
486
+
487
+ * **Identical API**: Your Work and Worker classes work the same on both versions
488
+ * **Automatic detection**: Fractor detects Ruby version and uses appropriate implementation
489
+ * **Performance**: Ruby 4.0's port-based communication may offer better performance for high-throughput scenarios
490
+ * **Compatibility**: Code written for Fractor works on both Ruby 3.x and 4.0+ without changes
491
+
492
+ [NOTE]
493
+ ====
494
+ The internal implementation difference is transparent to users. You write your
495
+ Workers and Work classes once, and Fractor handles the version-specific
496
+ communication patterns.
497
+ ====
498
+
499
+ === Backpressure mechanisms
500
+
501
+ Fractor implements several backpressure mechanisms to prevent overload:
502
+
503
+ ==== Queue-based backpressure
504
+
505
+ [source,ruby]
506
+ ----
507
+ # Work queue fills up when workers are slower than producers
508
+ queue = Fractor::WorkQueue.new
509
+
510
+ # Producer blocks when queue reaches capacity (if configured)
511
+ # Workers pull at their own pace
512
+ supervisor.register_work_source do
513
+ # This callback controls production rate
514
+ if queue.size < MAX_QUEUE_SIZE
515
+ fetch_new_work
516
+ else
517
+ nil # Skip this cycle, allow queue to drain
518
+ end
519
+ end
520
+ ----
521
+
522
+ ==== Worker pool saturation
523
+
524
+ [source]
525
+ ----
526
+ All Workers Busy
527
+
528
+ ├──> New work added to queue
529
+ │ │
530
+ │ ├──> Queue grows
531
+ │ │ │
532
+ │ │ ├──> Producer detects high queue size
533
+ │ │ │ │
534
+ │ │ │ └──> Slow down production
535
+ │ │ │
536
+ │ │ └──> OR Add more workers (scale up)
537
+ │ │
538
+ │ └──> Workers complete tasks
539
+ │ │
540
+ │ └──> Queue drains
541
+ │ │
542
+ │ └──> Production resumes normal rate
543
+
544
+ Workers Available
545
+ ----
546
+
547
+ ==== Priority-based flow control
548
+
549
+ Using [`PriorityWorkQueue`](../../lib/fractor/priority_work_queue.rb):
550
+
551
+ [source,ruby]
552
+ ----
553
+ # Critical work processed first
554
+ queue = Fractor::PriorityWorkQueue.new(
555
+ aging_enabled: true, # Prevent starvation
556
+ aging_threshold: 60 # Boost priority after 60s
557
+ )
558
+
559
+ # Low-priority work waits but won't starve
560
+ queue.push(PriorityWork.new(data, priority: :low))
561
+ queue.push(PriorityWork.new(urgent, priority: :critical))
562
+
563
+ # Critical work processed immediately
564
+ # Low-priority work eventually gets priority boost
565
+ ----
566
+
567
+ === Thread safety design
568
+
569
+ Fractor ensures thread safety through multiple mechanisms:
570
+
571
+ . *Immutable work objects*
572
+ +
573
+ [source,ruby]
574
+ ----
575
+ class MyWork < Fractor::Work
576
+ def initialize(data)
577
+ # Freeze data to ensure immutability
578
+ super(data.freeze)
579
+ end
580
+ end
581
+ ----
582
+
583
+ . *Isolated Ractor memory*
584
+ +
585
+ [source]
586
+ ----
587
+ Ractor A Memory Ractor B Memory
588
+ ┌─────────────┐ ┌─────────────┐
589
+ │ @counter │ │ @counter │
590
+ │ @state │ │ @state │
591
+ │ @worker │ │ @worker │
592
+ └─────────────┘ └─────────────┘
593
+ │ │
594
+ └────────╳───────────────┘
595
+ No shared access
596
+ ----
597
+
598
+ . *Thread-safe queues*
599
+ +
600
+ [source,ruby]
601
+ ----
602
+ # WorkQueue uses Thread::Queue internally
603
+ class WorkQueue
604
+ def initialize
605
+ @queue = Thread::Queue.new # Thread-safe
606
+ @mutex = Mutex.new # Additional safety
607
+ end
608
+ end
609
+ ----
610
+
611
+ . *Message copying*
612
+ +
613
+ [source]
614
+ ----
615
+ Send: work object copied → Ractor receives copy
616
+ Receive: result copied → Supervisor receives copy
617
+
618
+ No shared references between Ractors
619
+ ----
620
+
621
+ == Workflow architecture
622
+
623
+ === Workflow DSL implementation
624
+
625
+ The workflow DSL provides a declarative way to define complex processing graphs:
626
+
627
+ .Workflow definition structure
628
+ [source,ruby]
629
+ ----
630
+ class MyWorkflow < Fractor::Workflow
631
+ workflow "processing-pipeline" do
632
+ # Metadata
633
+ input_type InputModel
634
+ output_type OutputModel
635
+
636
+ # Job definitions with dependencies
637
+ job "parse", ParseWorker
638
+ job "validate", ValidateWorker, needs: "parse"
639
+ job "transform", TransformWorker, needs: "validate"
640
+ job "store", StoreWorker, needs: "transform", outputs: :workflow
641
+
642
+ # Workflow boundaries
643
+ start_with "parse"
644
+ end_with "store"
645
+ end
646
+ end
647
+ ----
648
+
649
+ === Dependency graph resolution
650
+
651
+ The workflow executor resolves dependencies using topological sorting:
652
+
653
+ [source]
654
+ ----
655
+ Dependency Graph:
656
+ parse
657
+
658
+
659
+ validate
660
+
661
+
662
+ transform
663
+
664
+
665
+ store
666
+
667
+ Execution Order (topological sort):
668
+ 1. parse (no dependencies)
669
+ 2. validate (depends on parse)
670
+ 3. transform (depends on validate)
671
+ 4. store (depends on transform)
672
+
673
+ Parallel Execution Where Possible:
674
+ ┌─────┐
675
+ │parse│
676
+ └──┬──┘
677
+ ├────────────┐
678
+ ▼ ▼
679
+ ┌────────┐ ┌─────────┐
680
+ │validate│ │summarize│ ← Can run in parallel
681
+ └────┬───┘ └────┬────┘
682
+ │ │
683
+ └─────┬─────┘
684
+
685
+ ┌─────────┐
686
+ │transform│
687
+ └─────────┘
688
+ ----
689
+
690
+ .Dependency resolution algorithm
691
+ [example]
692
+ ====
693
+ [source,ruby]
694
+ ----
695
+ # Pseudocode for dependency resolution
696
+ def resolve_execution_order(jobs)
697
+ # Build dependency graph
698
+ graph = build_graph(jobs)
699
+
700
+ # Find jobs with no dependencies (starting points)
701
+ ready = jobs.select { |j| j.dependencies.empty? }
702
+
703
+ # Topological sort
704
+ order = []
705
+ while ready.any?
706
+ # Process ready jobs
707
+ job = ready.shift
708
+ order << job
709
+
710
+ # Find newly ready jobs
711
+ jobs.each do |j|
712
+ if j.dependencies.all? { |dep| order.include?(dep) }
713
+ ready << j unless ready.include?(j)
714
+ end
715
+ end
716
+ end
717
+
718
+ order
719
+ end
720
+ ----
721
+ ====
722
+
723
+ === Execution engine design
724
+
725
+ The workflow executor manages job execution:
726
+
727
+ [source]
728
+ ----
729
+ WorkflowExecutor Components:
730
+
731
+ ┌───────────────────────────────────────────────┐
732
+ │ WorkflowExecutor │
733
+ │ │
734
+ │ ┌─────────────────────────────────────────┐ │
735
+ │ │ WorkflowContext │ │
736
+ │ │ - Input data │ │
737
+ │ │ - Job outputs │ │
738
+ │ │ - Execution state │ │
739
+ │ └─────────────────────────────────────────┘ │
740
+ │ │
741
+ │ ┌─────────────────────────────────────────┐ │
742
+ │ │ ExecutionTrace (optional) │ │
743
+ │ │ - Job start/end times │ │
744
+ │ │ - Data flow tracking │ │
745
+ │ │ - Performance metrics │ │
746
+ │ └─────────────────────────────────────────┘ │
747
+ │ │
748
+ │ ┌─────────────────────────────────────────┐ │
749
+ │ │ DeadLetterQueue (optional) │ │
750
+ │ │ - Failed job data │ │
751
+ │ │ - Error context │ │
752
+ │ │ - Retry tracking │ │
753
+ │ └─────────────────────────────────────────┘ │
754
+ └───────────────────────────────────────────────┘
755
+ ----
756
+
757
+ .Workflow execution flow
758
+ [source]
759
+ ----
760
+ 1. Initialization
761
+ └──> Create WorkflowContext
762
+ └──> Store input data
763
+ └──> Initialize job state
764
+
765
+ 2. Job Execution Loop
766
+ └──> For each job in execution order:
767
+ ├──> Check dependencies satisfied
768
+ │ └──> All required jobs completed?
769
+
770
+ ├──> Check conditional execution
771
+ │ └──> Evaluate if_condition if present
772
+
773
+ ├──> Prepare job input
774
+ │ └──> Gather outputs from dependencies
775
+ │ └──> Transform to job input type
776
+
777
+ ├──> Execute job
778
+ │ └──> Create supervisor for job workers
779
+ │ └──> Run work items through workers
780
+ │ └──> Collect results
781
+
782
+ ├──> Handle result
783
+ │ ├──> Success: Store output in context
784
+ │ └──> Error: Apply retry/circuit breaker logic
785
+
786
+ └──> Update trace (if enabled)
787
+
788
+ 3. Completion
789
+ └──> Extract workflow output
790
+ └──> Return WorkflowResult
791
+ ├──> Output data
792
+ ├──> Execution trace
793
+ └──> Success/failure status
794
+ ----
795
+
796
+ === State management
797
+
798
+ Workflow state is managed through [`WorkflowContext`](../../lib/fractor/workflow/workflow_context.rb):
799
+
800
+ [source,ruby]
801
+ ----
802
+ # WorkflowContext manages:
803
+ # 1. Input data
804
+ # 2. Job outputs
805
+ # 3. Execution metadata
806
+
807
+ context = WorkflowContext.new(input_data)
808
+
809
+ # Store job output
810
+ context.set_job_output("parse", parsed_data)
811
+
812
+ # Retrieve for next job
813
+ input_for_validate = context.get_job_output("parse")
814
+
815
+ # Type-safe access
816
+ context.get_job_output_as(OutputModel, "parse")
817
+ ----
818
+
819
+ State transitions:
820
+
821
+ [source]
822
+ ----
823
+ State Lifecycle:
824
+
825
+ PENDING ──> IN_PROGRESS ──> COMPLETED
826
+ │ │ │
827
+ │ │ └──> Output stored
828
+ │ │
829
+ │ └──> ERROR ──> RETRY ──> IN_PROGRESS
830
+ │ │
831
+ │ └──> FAILED (max retries)
832
+
833
+ └──> SKIPPED (conditional not met)
834
+ ----
835
+
836
+ == Queue architecture
837
+
838
+ === Work queue implementation
839
+
840
+ Basic work queue using Ruby's thread-safe `Thread::Queue`:
841
+
842
+ [source,ruby]
843
+ ----
844
+ class WorkQueue
845
+ def initialize
846
+ @queue = Thread::Queue.new
847
+ @mutex = Mutex.new
848
+ end
849
+
850
+ def <<(work_item)
851
+ validate_work!(work_item)
852
+ @queue << work_item
853
+ end
854
+
855
+ def pop_batch(max_items = 10)
856
+ items = []
857
+ max_items.times do
858
+ break if @queue.empty?
859
+ items << @queue.pop(true) rescue break
860
+ end
861
+ items
862
+ end
863
+ end
864
+ ----
865
+
866
+ Features:
867
+
868
+ * Thread-safe operations
869
+ * Batch retrieval for efficiency
870
+ * FIFO ordering
871
+ * Non-blocking pop option
872
+
873
+ === Priority queue design
874
+
875
+ Priority queue with aging to prevent starvation:
876
+
877
+ [source]
878
+ ----
879
+ PriorityWorkQueue Structure:
880
+
881
+ Priority Levels (0-4):
882
+ 0: Critical ← Highest priority
883
+ 1: High
884
+ 2: Normal ← Default
885
+ 3: Low
886
+ 4: Background ← Lowest priority
887
+
888
+ Internal Storage:
889
+ ┌────────────────────────────────────────┐
890
+ │ Sorted Array │
891
+ │ ┌──────────────────────────────────┐ │
892
+ │ │ [Critical Work 1] age: 10s │ │
893
+ │ │ [Critical Work 2] age: 5s │ │
894
+ │ │ [High Work 1] age: 30s │ │
895
+ │ │ [Normal Work 1] age: 45s │ │
896
+ │ │ [Low Work 1] age: 70s ⚡ │ │ ← Age boost!
897
+ │ └──────────────────────────────────┘ │
898
+ └────────────────────────────────────────┘
899
+
900
+ Aging Mechanism:
901
+ If work.age >= aging_threshold (60s):
902
+ Effective priority increases
903
+ Prevents starvation of low-priority items
904
+ ----
905
+
906
+ .Priority calculation with aging
907
+ [example]
908
+ ====
909
+ [source,ruby]
910
+ ----
911
+ # Effective priority = base_priority - (age / threshold).floor
912
+ work = PriorityWork.new(data, priority: :low) # base = 3
913
+
914
+ # After 0 seconds: effective = 3 - 0 = 3 (low)
915
+ # After 60 seconds: effective = 3 - 1 = 2 (normal)
916
+ # After 120 seconds: effective = 3 - 2 = 1 (high)
917
+ # After 180 seconds: effective = 3 - 3 = 0 (critical)
918
+ ----
919
+ ====
920
+
921
+ === Queue selection strategies
922
+
923
+ Choosing the right queue for your needs:
924
+
925
+ [cols="2,3,3,2"]
926
+ |===
927
+ |Queue Type |Use Case |Benefits |Trade-offs
928
+
929
+ |`WorkQueue`
930
+ |General-purpose processing
931
+ |Simple, FIFO, predictable
932
+ |No prioritization
933
+
934
+ |`PriorityWorkQueue`
935
+ |Mixed-priority workloads
936
+ |Priority handling, aging
937
+ |Slight overhead
938
+
939
+ |`Thread::Queue`
940
+ |Simple threading
941
+ |Built-in, minimal overhead
942
+ |Basic features only
943
+
944
+ |External (Redis)
945
+ |Distributed systems
946
+ |Persistence, clustering
947
+ |Network latency
948
+ |===
949
+
950
+ === Continuous server integration
951
+
952
+ [`ContinuousServer`](../../lib/fractor/continuous_server.rb) provides high-level queue integration:
953
+
954
+ [source,ruby]
955
+ ----
956
+ # Automatic queue integration
957
+ queue = Fractor::WorkQueue.new
958
+ server = Fractor::ContinuousServer.new(
959
+ worker_pools: [{ worker_class: MyWorker }],
960
+ work_queue: queue # Auto-registered
961
+ )
962
+
963
+ # Queue automatically polled
964
+ # No manual work source registration needed
965
+ server.run
966
+ ----
967
+
968
+ Internal architecture:
969
+
970
+ [source]
971
+ ----
972
+ ContinuousServer Architecture:
973
+
974
+ ┌────────────────────────────────────────────────┐
975
+ │ ContinuousServer │
976
+ │ ┌──────────────────────────────────────────┐ │
977
+ │ │ Main Thread │ │
978
+ │ │ - Signal handling │ │
979
+ │ │ - Lifecycle management │ │
980
+ │ └──────────────────────────────────────────┘ │
981
+ │ │
982
+ │ ┌──────────────────────────────────────────┐ │
983
+ │ │ Supervisor Thread │ │
984
+ │ │ - Work distribution │ │
985
+ │ │ - Ractor management │ │
986
+ │ └──────────────────────────────────────────┘ │
987
+ │ │
988
+ │ ┌──────────────────────────────────────────┐ │
989
+ │ │ Results Thread │ │
990
+ │ │ - Callback execution │ │
991
+ │ │ - Error handling │ │
992
+ │ └──────────────────────────────────────────┘ │
993
+ └────────────────────────────────────────────────┘
994
+ ----
995
+
996
+ == Error handling architecture
997
+
998
+ === Error propagation model
999
+
1000
+ Error propagation follows a clear path from worker to handler:
1001
+
1002
+ [source]
1003
+ ----
1004
+ Error Origin Error Handling
1005
+ │ │
1006
+ ▼ ▼
1007
+ ┌──────────┐ ┌──────────┐
1008
+ │ Worker │ │ Handler │
1009
+ │ process()│ │ Callback │
1010
+ └────┬─────┘ └────▲─────┘
1011
+ │ │
1012
+ │ raise StandardError │
1013
+ ▼ │
1014
+ ┌──────────┐ │
1015
+ │ Ractor │ │
1016
+ │ rescue │ │
1017
+ └────┬─────┘ │
1018
+ │ │
1019
+ │ Create WorkResult │
1020
+ │ with error details │
1021
+ ▼ │
1022
+ ┌────────────┐ │
1023
+ │ Supervisor │ │
1024
+ │ select │ │
1025
+ └────┬───────┘ │
1026
+ │ │
1027
+ │ Receive :error message │
1028
+ ▼ │
1029
+ ┌──────────────┐ │
1030
+ │ResultAggregator │
1031
+ │ @errors << result │
1032
+ └────┬─────────┘ │
1033
+ │ │
1034
+ │ Trigger callbacks │
1035
+ └────────────────────────────┘
1036
+ ----
1037
+
1038
+ No errors are silently swallowed—all failures captured and exposed.
1039
+
1040
+ === Retry mechanism design
1041
+
1042
+ Workflow retry configuration:
1043
+
1044
+ [source,ruby]
1045
+ ----
1046
+ # Job-level retry configuration
1047
+ job "fetch_data", APIWorker do
1048
+ retry_on NetworkError, max_attempts: 3, backoff: :exponential
1049
+ retry_on Timeout::Error, max_attempts: 5, backoff: :linear
1050
+ end
1051
+ ----
1052
+
1053
+ Retry state machine:
1054
+
1055
+ [source]
1056
+ ----
1057
+ ┌─────────┐
1058
+ │ EXECUTE │
1059
+ └────┬────┘
1060
+
1061
+ ┌────────┴────────┐
1062
+ │ │
1063
+ Success Failure
1064
+ │ │
1065
+ ▼ ▼
1066
+ ┌─────────┐ ┌─────────┐
1067
+ │COMPLETED│ │ ERROR │
1068
+ └─────────┘ └────┬────┘
1069
+
1070
+ Check retry policy
1071
+
1072
+ ┌────────────┴────────────┐
1073
+ │ │
1074
+ Retriable Not retriable
1075
+ (attempts < max) (or max reached)
1076
+ │ │
1077
+ ▼ ▼
1078
+ ┌─────────┐ ┌─────────┐
1079
+ │ RETRY │ │ FAILED │
1080
+ └────┬────┘ └─────────┘
1081
+
1082
+ Apply backoff delay
1083
+
1084
+ └──> EXECUTE (again)
1085
+ ----
1086
+
1087
+ === Circuit breaker implementation
1088
+
1089
+ Circuit breaker prevents cascading failures:
1090
+
1091
+ [source]
1092
+ ----
1093
+ Circuit Breaker States:
1094
+
1095
+ CLOSED (Normal Operation)
1096
+
1097
+ │ Failures < threshold
1098
+
1099
+ Success ←──────┐
1100
+
1101
+ Failure ───────┤
1102
+
1103
+ Failures >= threshold
1104
+
1105
+
1106
+ OPEN (Failing Fast)
1107
+
1108
+ │ All requests fail immediately
1109
+ │ Wait timeout period
1110
+
1111
+
1112
+ HALF-OPEN (Testing)
1113
+
1114
+ │ Allow 1 request through
1115
+
1116
+ ├──> Success ──> CLOSED
1117
+
1118
+ └──> Failure ──> OPEN
1119
+ ----
1120
+
1121
+ Configuration:
1122
+
1123
+ [source,ruby]
1124
+ ----
1125
+ job "external_api", APIWorker do
1126
+ circuit_breaker(
1127
+ failure_threshold: 5, # Open after 5 failures
1128
+ timeout: 60, # Try again after 60s
1129
+ half_open_attempts: 3 # Test with 3 requests
1130
+ )
1131
+ end
1132
+ ----
1133
+
1134
+ === Dead letter queue architecture
1135
+
1136
+ Failed items sent to DLQ for analysis and retry:
1137
+
1138
+ [source]
1139
+ ----
1140
+ Dead Letter Queue Structure:
1141
+
1142
+ ┌─────────────────────────────────────────────────┐
1143
+ │ DeadLetterQueue │
1144
+ │ │
1145
+ │ ┌───────────────────────────────────────────┐ │
1146
+ │ │ Entry Storage │ │
1147
+ │ │ - Work item │ │
1148
+ │ │ - Error details │ │
1149
+ │ │ - Timestamp │ │
1150
+ │ │ - Attempt count │ │
1151
+ │ │ - Context data │ │
1152
+ │ └───────────────────────────────────────────┘ │
1153
+ │ │
1154
+ │ ┌───────────────────────────────────────────┐ │
1155
+ │ │ Persister (optional) │ │
1156
+ │ │ - Save to disk/database │ │
1157
+ │ │ - Enable crash recovery │ │
1158
+ │ └───────────────────────────────────────────┘ │
1159
+ │ │
1160
+ │ ┌───────────────────────────────────────────┐ │
1161
+ │ │ Callbacks │ │
1162
+ │ │ - on_add: Notification │ │
1163
+ │ │ - Custom processing │ │
1164
+ │ └───────────────────────────────────────────┘ │
1165
+ └─────────────────────────────────────────────────┘
1166
+ ----
1167
+
1168
+ Usage:
1169
+
1170
+ [source,ruby]
1171
+ ----
1172
+ # Configure DLQ for workflow
1173
+ class MyWorkflow < Fractor::Workflow
1174
+ workflow "processing" do
1175
+ configure_dead_letter_queue(
1176
+ max_size: 1000,
1177
+ persister: DiskPersister.new("dlq"),
1178
+ on_add: ->(entry) { alert_ops(entry) }
1179
+ )
1180
+
1181
+ # Jobs...
1182
+ end
1183
+ end
1184
+
1185
+ # Access DLQ after execution
1186
+ result = workflow.execute(input: data)
1187
+ failed_items = workflow.dead_letter_queue.entries
1188
+ ----
1189
+
1190
+ == Sequence diagrams
1191
+
1192
+ === Pipeline mode execution
1193
+
1194
+ [source]
1195
+ ----
1196
+ Client Supervisor WorkQueue Ractor Worker
1197
+ │ │ │ │ │
1198
+ ├─ new ──► │ │ │ │
1199
+ │ │ │ │ │
1200
+ ├─ add_work_items ──────► │ │ │
1201
+ │ │ │ │ │
1202
+ ├─ run ───► │ │ │ │
1203
+ │ │ │ │ │
1204
+ │ ├─ start_workers ───────► │ │
1205
+ │ │ │ │ │
1206
+ │ │ │ ├─ new ─► │
1207
+ │ │ │ │ │
1208
+ │ │◄──── :initialize ───────┤ │
1209
+ │ │ │ │ │
1210
+ │ ├──── pop ──► │ │ │
1211
+ │ │ │ │ │
1212
+ │ │◄──── work ──┤ │ │
1213
+ │ │ │ │ │
1214
+ │ ├──── work ─────────────► │ │
1215
+ │ │ │ │ │
1216
+ │ │ │ ├─ process ─► │
1217
+ │ │ │ │ │
1218
+ │ │ │ │◄── result ──┤
1219
+ │ │ │ │ │
1220
+ │ │◄──── :result ───────────┤ │
1221
+ │ │ │ │ │
1222
+ │ ├─ add_result │ │ │
1223
+ │ │ │ │ │
1224
+ │ ├──── pop ──► │ │ │
1225
+ │ │ │ │ │
1226
+ │ │◄──── work ──┤ │ │
1227
+ │ │ │ │ │
1228
+ │ ├──── work ─────────────► │ │
1229
+ │ │ │ │ │
1230
+ │ ... ... ... ...
1231
+ │ │ │ │ │
1232
+ │ ├─ (all work done) │ │
1233
+ │ │ │ │ │
1234
+ │◄─ results ┤ │ │ │
1235
+ │ │ │ │ │
1236
+ ----
1237
+
1238
+ === Continuous mode execution
1239
+
1240
+ [source]
1241
+ ----
1242
+ Client ContinuousServer Supervisor WorkSource Ractor Worker
1243
+ │ │ │ │ │ │
1244
+ ├─ new ───► │ │ │ │ │
1245
+ │ │ │ │ │ │
1246
+ ├─ on_result(callback) ──► │ │ │ │
1247
+ │ │ │ │ │ │
1248
+ ├─ run ────► │ │ │ │ │
1249
+ │ │ │ │ │ │
1250
+ │ ├─ Thread.new ─► │ │ │ │
1251
+ │ │ │ │ │ │
1252
+ │ │ ├─ start_workers ─────► │ │
1253
+ │ │ │ │ │ │
1254
+ │ │ │ │ ├─ new ─► │
1255
+ │ │ │ │ │ │
1256
+ │ │ │◄─ :initialize ───────┤ │
1257
+ │ │ │ │ │ │
1258
+ │ [Main loop] │ │ │ │
1259
+ │ │ │ │ │ │
1260
+ │ │ ├─ poll ──► │ │ │
1261
+ │ │ │ │ │ │
1262
+ │ │ │◄─ [work] ─┤ │ │
1263
+ │ │ │ │ │ │
1264
+ │ │ ├─ add_work │ │ │
1265
+ │ │ │ │ │ │
1266
+ │ │ ├─ send ──────────────► │ │
1267
+ │ │ │ │ │ │
1268
+ │ │ │ │ ├─ process ─► │
1269
+ │ │ │ │ │ │
1270
+ │ │ │ │ │◄─ result ──┤
1271
+ │ │ │ │ │ │
1272
+ │ │ │◄─ :result ───────────┤ │
1273
+ │ │ │ │ │ │
1274
+ │ │ ├─ add_result │ │
1275
+ │ │ │ │ │ │
1276
+ │ │◄─ result ─────┤ │ │ │
1277
+ │ │ │ │ │ │
1278
+ │ ├─ callback() │ │ │ │
1279
+ │ │ │ │ │ │
1280
+ │ [Loop continues] │ │ │ │
1281
+ │ │ │ │ │ │
1282
+ ├─ SIGINT ─► │ │ │ │ │
1283
+ │ │ │ │ │ │
1284
+ │ ├─ stop ───────► │ │ │ │
1285
+ │ │ │ │ │ │
1286
+ │ │ ├─ :shutdown ─────────► │ │
1287
+ │ │ │ │ │ │
1288
+ │ │ │◄─ :shutdown ─────────┤ │
1289
+ │ │ │ │ │ │
1290
+ │ │◄─ cleanup ────┤ │ │ │
1291
+ │ │ │ │ │ │
1292
+ │◄─ exit ───┤ │ │ │ │
1293
+ │ │ │ │ │ │
1294
+ ----
1295
+
1296
+ === Workflow execution sequence
1297
+
1298
+ [source]
1299
+ ----
1300
+ Client Workflow Executor Job1 Supervisor Ractor Worker
1301
+ │ │ │ │ │ │ │
1302
+ ├─ new ─► │ │ │ │ │ │
1303
+ │ │ │ │ │ │ │
1304
+ ├─ execute(input) ► │ │ │ │ │
1305
+ │ │ │ │ │ │ │
1306
+ │ │ ├─ resolve dependencies │ │
1307
+ │ │ │ │ │ │ │
1308
+ │ │ ├─ execute(Job1) ► │ │
1309
+ │ │ │ │ │ │ │
1310
+ │ │ │ ├─ new ─► │ │
1311
+ │ │ │ │ │ │ │
1312
+ │ │ │ │ ├─ start ► │ │
1313
+ │ │ │ │ │ │ │
1314
+ │ │ │ │ │ ├─ new ─► │
1315
+ │ │ │ │ │ │ │
1316
+ │ │ │ │ ├─ run │ │
1317
+ │ │ │ │ │ │ │
1318
+ │ │ │ │ │◄─ result ─ │
1319
+ │ │ │ │ │ │ │
1320
+ │ │ │ │◄─ results ─────── │
1321
+ │ │ │ │ │ │ │
1322
+ │ │ │◄─ job1_output ─┤ │ │
1323
+ │ │ │ │ │ │ │
1324
+ │ │ ├─ execute(Job2, input: job1_output)
1325
+ │ │ │ │ │ │ │
1326
+ │ │ ... ... ... ... ...
1327
+ │ │ │ │ │ │ │
1328
+ │ │◄─ WorkflowResult ┤ │ │ │
1329
+ │ │ │ │ │ │ │
1330
+ │◄─ result │ │ │ │ │ │
1331
+ │ │ │ │ │ │ │
1332
+ ----
1333
+
1334
+ == Performance considerations
1335
+
1336
+ === Monitoring and optimization
1337
+
1338
+ Key metrics to monitor:
1339
+
1340
+ [cols="2,3,2"]
1341
+ |===
1342
+ |Metric |Description |Target
1343
+
1344
+ |Worker utilization
1345
+ |% of time workers are busy
1346
+ |> 80%
1347
+
1348
+ |Queue depth
1349
+ |Number of items in queue
1350
+ |< 1000
1351
+
1352
+ |Processing latency
1353
+ |Time from submission to completion
1354
+ |Minimize
1355
+
1356
+ |Error rate
1357
+ |% of failed work items
1358
+ |< 1%
1359
+
1360
+ |Memory per Ractor
1361
+ |RAM used by each Ractor
1362
+ |< 100MB
1363
+
1364
+ |GC frequency
1365
+ |Garbage collection cycles
1366
+ |Minimize
1367
+ |===
1368
+
1369
+ .Optimization checklist
1370
+ [example]
1371
+ ====
1372
+ * Optimize worker count based on workload type (CPU vs I/O)
1373
+ * Use priority queues for mixed-criticality workloads
1374
+ * Implement backpressure to prevent overload
1375
+ * Monitor queue depth and adjust production rate
1376
+ * Profile memory usage and optimize large objects
1377
+ * Use callbacks for immediate result processing
1378
+ * Enable workflow tracing only when needed
1379
+ * Batch work items where possible
1380
+ ====
1381
+
1382
+ == Next steps
1383
+
1384
+ * Read link:design-principles/[Design Principles] for philosophy and rationale
1385
+ * Explore link:core-concepts/[Core Concepts] for component details
1386
+ * Try link:../guides/pipeline-mode/[Pipeline Mode] for batch processing
1387
+ * Try link:../guides/continuous-mode/[Continuous Mode] for long-running services
1388
+ * Learn link:../features/workflows/[Workflows] for complex processing
1389
+ * Review link:../features/error-handling/[Error Handling] strategies
1390
+ * Study link:../reference/examples/[Examples] for real-world patterns