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
@@ -5,11 +5,14 @@ require_relative "../../lib/fractor"
5
5
  module SpecializedWorkers
6
6
  # First work type: Compute-intensive operations
7
7
  class ComputeWork < Fractor::Work
8
+ attr_reader :work_type
9
+
8
10
  def initialize(data, operation = :default, parameters = {})
9
11
  super({
10
12
  data: data,
11
13
  operation: operation,
12
- parameters: parameters
14
+ parameters: parameters,
15
+ work_type: :compute, # Add work type identifier for Ractor compatibility
13
16
  })
14
17
  end
15
18
 
@@ -25,6 +28,10 @@ module SpecializedWorkers
25
28
  input[:parameters]
26
29
  end
27
30
 
31
+ def work_type
32
+ input[:work_type]
33
+ end
34
+
28
35
  def to_s
29
36
  "ComputeWork: operation=#{operation}, parameters=#{parameters}"
30
37
  end
@@ -32,12 +39,16 @@ module SpecializedWorkers
32
39
 
33
40
  # Second work type: Database operations
34
41
  class DatabaseWork < Fractor::Work
35
- def initialize(data = "", query_type = :select, table = "unknown", conditions = {})
42
+ attr_reader :work_type
43
+
44
+ def initialize(data = "", query_type = :select, table = "unknown",
45
+ conditions = {})
36
46
  super({
37
47
  data: data,
38
48
  query_type: query_type,
39
49
  table: table,
40
- conditions: conditions
50
+ conditions: conditions,
51
+ work_type: :database, # Add work type identifier for Ractor compatibility
41
52
  })
42
53
  end
43
54
 
@@ -57,6 +68,10 @@ module SpecializedWorkers
57
68
  input[:conditions]
58
69
  end
59
70
 
71
+ def work_type
72
+ input[:work_type]
73
+ end
74
+
60
75
  def to_s
61
76
  "DatabaseWork: query_type=#{query_type}, table=#{table}, conditions=#{conditions}"
62
77
  end
@@ -64,24 +79,28 @@ module SpecializedWorkers
64
79
 
65
80
  # First worker type: Handles compute-intensive operations
66
81
  class ComputeWorker < Fractor::Worker
67
- def initialize
82
+ def initialize(name: nil, **options)
83
+ super
68
84
  # Setup resources needed for computation
69
- @compute_resources = { memory: 1024, cpu_cores: 4 }
85
+ # Use Ractor.make_shareable to make the hash shareable across Ractors
86
+ @compute_resources = Ractor.make_shareable({ memory: 1024, cpu_cores: 4 })
70
87
  end
71
88
 
72
89
  def process(work)
73
- # Only handle ComputeWork
74
- unless work.is_a?(ComputeWork)
90
+ # Only handle ComputeWork - check work_type for Ractor compatibility
91
+ unless work.respond_to?(:work_type) && work.work_type == :compute
75
92
  return Fractor::WorkResult.new(
76
93
  error: "ComputeWorker can only process ComputeWork, got: #{work.class}",
77
- work: work
94
+ work: work,
78
95
  )
79
96
  end
80
97
 
81
98
  # Process based on the requested operation
82
99
  result = case work.operation
83
- when :matrix_multiply then matrix_multiply(work.data, work.parameters)
84
- when :image_transform then image_transform(work.data, work.parameters)
100
+ when :matrix_multiply then matrix_multiply(work.data,
101
+ work.parameters)
102
+ when :image_transform then image_transform(work.data,
103
+ work.parameters)
85
104
  when :path_finding then path_finding(work.data, work.parameters)
86
105
  else default_computation(work.data, work.parameters)
87
106
  end
@@ -90,9 +109,9 @@ module SpecializedWorkers
90
109
  result: {
91
110
  operation: work.operation,
92
111
  computation_result: result,
93
- resources_used: @compute_resources
112
+ resources_used: @compute_resources,
94
113
  },
95
- work: work
114
+ work: work,
96
115
  )
97
116
  end
98
117
 
@@ -110,7 +129,7 @@ module SpecializedWorkers
110
129
  # Simulate image transformation
111
130
  sleep(rand(0.1..0.3))
112
131
  transforms = params[:transforms] || %i[rotate scale]
113
- "Image transformation applied: #{transforms.join(", ")} with parameters #{params}"
132
+ "Image transformation applied: #{transforms.join(', ')} with parameters #{params}"
114
133
  end
115
134
 
116
135
  def path_finding(_data, params)
@@ -130,17 +149,19 @@ module SpecializedWorkers
130
149
 
131
150
  # Second worker type: Handles database operations
132
151
  class DatabaseWorker < Fractor::Worker
133
- def initialize
152
+ def initialize(name: nil, **options)
153
+ super
134
154
  # Setup database connection and resources
135
- @db_connection = { pool_size: 5, timeout: 30 }
155
+ # Use Ractor.make_shareable to make the hash shareable across Ractors
156
+ @db_connection = Ractor.make_shareable({ pool_size: 5, timeout: 30 })
136
157
  end
137
158
 
138
159
  def process(work)
139
- # Only handle DatabaseWork
140
- unless work.is_a?(DatabaseWork)
160
+ # Only handle DatabaseWork - check work_type for Ractor compatibility
161
+ unless work.respond_to?(:work_type) && work.work_type == :database
141
162
  return Fractor::WorkResult.new(
142
163
  error: "DatabaseWorker can only process DatabaseWork, got: #{work.class}",
143
- work: work
164
+ work: work,
144
165
  )
145
166
  end
146
167
 
@@ -148,7 +169,8 @@ module SpecializedWorkers
148
169
  result = case work.query_type
149
170
  when :select then perform_select(work.table, work.conditions)
150
171
  when :insert then perform_insert(work.table, work.data)
151
- when :update then perform_update(work.table, work.data, work.conditions)
172
+ when :update then perform_update(work.table, work.data,
173
+ work.conditions)
152
174
  when :delete then perform_delete(work.table, work.conditions)
153
175
  else default_query(work.query_type, work.table, work.conditions)
154
176
  end
@@ -159,9 +181,9 @@ module SpecializedWorkers
159
181
  table: work.table,
160
182
  rows_affected: result[:rows_affected],
161
183
  data: result[:data],
162
- execution_time: result[:time]
184
+ execution_time: result[:time],
163
185
  },
164
- work: work
186
+ work: work,
165
187
  )
166
188
  end
167
189
 
@@ -174,8 +196,10 @@ module SpecializedWorkers
174
196
  record_count = rand(0..20)
175
197
  {
176
198
  rows_affected: record_count,
177
- data: record_count.times.map { |i| { id: i + 1, name: "Record #{i + 1}" } },
178
- time: rand(0.01..0.05)
199
+ data: Array.new(record_count) do |i|
200
+ { id: i + 1, name: "Record #{i + 1}" }
201
+ end,
202
+ time: rand(0.01..0.05),
179
203
  }
180
204
  end
181
205
 
@@ -185,7 +209,7 @@ module SpecializedWorkers
185
209
  {
186
210
  rows_affected: 1,
187
211
  data: { id: rand(1000..9999) },
188
- time: rand(0.01..0.03)
212
+ time: rand(0.01..0.03),
189
213
  }
190
214
  end
191
215
 
@@ -196,7 +220,7 @@ module SpecializedWorkers
196
220
  {
197
221
  rows_affected: affected,
198
222
  data: nil,
199
- time: rand(0.01..0.05)
223
+ time: rand(0.01..0.05),
200
224
  }
201
225
  end
202
226
 
@@ -207,7 +231,7 @@ module SpecializedWorkers
207
231
  {
208
232
  rows_affected: affected,
209
233
  data: nil,
210
- time: rand(0.01..0.03)
234
+ time: rand(0.01..0.03),
211
235
  }
212
236
  end
213
237
 
@@ -217,7 +241,7 @@ module SpecializedWorkers
217
241
  {
218
242
  rows_affected: 0,
219
243
  data: nil,
220
- time: rand(0.005..0.01)
244
+ time: rand(0.005..0.01),
221
245
  }
222
246
  end
223
247
  end
@@ -230,14 +254,14 @@ module SpecializedWorkers
230
254
  # Create separate supervisors for each worker type
231
255
  @compute_supervisor = Fractor::Supervisor.new(
232
256
  worker_pools: [
233
- { worker_class: ComputeWorker, num_workers: compute_workers }
234
- ]
257
+ { worker_class: ComputeWorker, num_workers: compute_workers },
258
+ ],
235
259
  )
236
260
 
237
261
  @db_supervisor = Fractor::Supervisor.new(
238
262
  worker_pools: [
239
- { worker_class: DatabaseWorker, num_workers: db_workers }
240
- ]
263
+ { worker_class: DatabaseWorker, num_workers: db_workers },
264
+ ],
241
265
  )
242
266
 
243
267
  @compute_results = []
@@ -249,13 +273,20 @@ module SpecializedWorkers
249
273
  compute_work_items = compute_tasks.map do |task|
250
274
  ComputeWork.new(task[:data], task[:operation], task[:parameters])
251
275
  end
276
+ puts "Created #{compute_work_items.size} compute work items"
277
+ puts "First compute work item: #{compute_work_items.first.inspect}" if compute_work_items.any?
252
278
  @compute_supervisor.add_work_items(compute_work_items)
279
+ puts "Added compute work items to supervisor"
253
280
 
254
281
  # Create and add database work items
255
282
  db_work_items = db_tasks.map do |task|
256
- DatabaseWork.new(task[:data], task[:query_type], task[:table], task[:conditions])
283
+ DatabaseWork.new(task[:data], task[:query_type], task[:table],
284
+ task[:conditions])
257
285
  end
286
+ puts "Created #{db_work_items.size} database work items"
287
+ puts "First db work item: #{db_work_items.first.inspect}" if db_work_items.any?
258
288
  @db_supervisor.add_work_items(db_work_items)
289
+ puts "Added database work items to supervisor"
259
290
 
260
291
  # Run the supervisors directly - this is more reliable
261
292
  @compute_supervisor.run
@@ -266,7 +297,19 @@ module SpecializedWorkers
266
297
  db_results_agg = @db_supervisor.results
267
298
 
268
299
  puts "Received compute results: #{compute_results_agg.results.size} items"
300
+ puts "Received compute errors: #{compute_results_agg.errors.size} items"
301
+ if compute_results_agg.errors.any?
302
+ compute_results_agg.errors.each do |e|
303
+ puts " Error: #{e.error}"
304
+ end
305
+ end
269
306
  puts "Received database results: #{db_results_agg.results.size} items"
307
+ puts "Received database errors: #{db_results_agg.errors.size} items"
308
+ if db_results_agg.errors.any?
309
+ db_results_agg.errors.each do |e|
310
+ puts " Error: #{e.error}"
311
+ end
312
+ end
270
313
 
271
314
  # Format and store results
272
315
  @compute_results = format_compute_results(compute_results_agg)
@@ -277,13 +320,13 @@ module SpecializedWorkers
277
320
  computation: {
278
321
  tasks: compute_tasks.size,
279
322
  completed: @compute_results.size,
280
- results: @compute_results
323
+ results: @compute_results,
281
324
  },
282
325
  database: {
283
326
  tasks: db_tasks.size,
284
327
  completed: @db_results.size,
285
- results: @db_results
286
- }
328
+ results: @db_results,
329
+ },
287
330
  }
288
331
  end
289
332
 
@@ -316,18 +359,18 @@ if __FILE__ == $PROGRAM_NAME
316
359
  {
317
360
  operation: :matrix_multiply,
318
361
  data: "Matrix data...",
319
- parameters: { size: [10, 10] }
362
+ parameters: { size: [10, 10] },
320
363
  },
321
364
  {
322
365
  operation: :image_transform,
323
366
  data: "Image data...",
324
- parameters: { transforms: %i[rotate scale blur], angle: 45, scale: 1.5 }
367
+ parameters: { transforms: %i[rotate scale blur], angle: 45, scale: 1.5 },
325
368
  },
326
369
  {
327
370
  operation: :path_finding,
328
371
  data: "Graph data...",
329
- parameters: { algorithm: :dijkstra, nodes: 20, start: 1, end: 15 }
330
- }
372
+ parameters: { algorithm: :dijkstra, nodes: 20, start: 1, end: 15 },
373
+ },
331
374
  ]
332
375
 
333
376
  # Prepare database tasks
@@ -335,24 +378,24 @@ if __FILE__ == $PROGRAM_NAME
335
378
  {
336
379
  query_type: :select,
337
380
  table: "users",
338
- conditions: { active: true, role: "admin" }
381
+ conditions: { active: true, role: "admin" },
339
382
  },
340
383
  {
341
384
  query_type: :insert,
342
385
  table: "orders",
343
- data: "Order data..."
386
+ data: "Order data...",
344
387
  },
345
388
  {
346
389
  query_type: :update,
347
390
  table: "products",
348
391
  data: "Product data...",
349
- conditions: { category: "electronics" }
392
+ conditions: { category: "electronics" },
350
393
  },
351
394
  {
352
395
  query_type: :delete,
353
396
  table: "sessions",
354
- conditions: { expired: true }
355
- }
397
+ conditions: { expired: true },
398
+ },
356
399
  ]
357
400
 
358
401
  compute_workers = 2
@@ -363,7 +406,7 @@ if __FILE__ == $PROGRAM_NAME
363
406
  start_time = Time.now
364
407
  system = SpecializedWorkers::HybridSystem.new(
365
408
  compute_workers: compute_workers,
366
- db_workers: db_workers
409
+ db_workers: db_workers,
367
410
  )
368
411
  result = system.process_mixed_workload(compute_tasks, db_tasks)
369
412
  end_time = Time.now
@@ -0,0 +1,206 @@
1
+ = Real-Time Data Stream Processor Example
2
+ :toc:
3
+ :toclevels: 3
4
+
5
+ Real-time event stream processor using Fractor::ContinuousServer for processing continuous data streams with windowing, metrics tracking, and graceful shutdown.
6
+
7
+ == Purpose
8
+
9
+ This example demonstrates:
10
+
11
+ * Continuous event processing with Fractor::ContinuousServer
12
+ * Sliding window aggregation (5-second windows)
13
+ * Real-time metrics tracking
14
+ * Backpressure handling
15
+ * Graceful shutdown with SIGINT/SIGTERM
16
+ * Performance monitoring
17
+
18
+ == Features
19
+
20
+ === Continuous Processing
21
+
22
+ Processes events as they arrive without stopping:
23
+
24
+ * Non-blocking event ingestion
25
+ * Parallel processing across workers
26
+ * Result collection in background thread
27
+ * Automatic workload distribution
28
+
29
+ === Sliding Window Aggregation
30
+
31
+ Maintains a sliding time window of recent events:
32
+
33
+ * Configurable window size (default: 5 seconds)
34
+ * Automatic cleanup of old events
35
+ * Window-based metrics calculation
36
+ * Real-time window size tracking
37
+
38
+ === Performance Metrics
39
+
40
+ Tracks and displays real-time metrics:
41
+
42
+ * Total events processed
43
+ * Events per second
44
+ * Average processing time
45
+ * Current window size
46
+
47
+ === Graceful Shutdown
48
+
49
+ Handles termination signals properly:
50
+
51
+ * SIGINT (Ctrl+C) for manual shutdown
52
+ * SIGTERM for system shutdown
53
+ * Preserves in-flight work
54
+ * Final summary report
55
+
56
+ == Usage
57
+
58
+ === Basic Usage
59
+
60
+ Run with default settings (30 seconds, 10 events/second):
61
+
62
+ [source,bash]
63
+ ----
64
+ ruby stream_processor.rb
65
+ ----
66
+
67
+ === Custom Duration
68
+
69
+ Process for 60 seconds:
70
+
71
+ [source,bash]
72
+ ----
73
+ ruby stream_processor.rb -d 60
74
+ ----
75
+
76
+ === Custom Event Rate
77
+
78
+ Generate 50 events per second:
79
+
80
+ [source,bash]
81
+ ----
82
+ ruby stream_processor.rb -r 50
83
+ ----
84
+
85
+ === Custom Workers
86
+
87
+ Use 8 worker ractors:
88
+
89
+ [source,bash]
90
+ ----
91
+ ruby stream_processor.rb -w 8
92
+ ----
93
+
94
+ === Custom Window Size
95
+
96
+ Use 10-second window:
97
+
98
+ [source,bash]
99
+ ----
100
+ ruby stream_processor.rb --window 10
101
+ ----
102
+
103
+ === Combined Options
104
+
105
+ [source,bash]
106
+ ----
107
+ ruby stream_processor.rb -w 8 -d 120 -r 100 --window 10
108
+ ----
109
+
110
+ == Examples
111
+
112
+ === Example 1: Default Settings
113
+
114
+ [source,bash]
115
+ ----
116
+ $ ruby stream_processor.rb
117
+
118
+ Starting Stream Processor...
119
+ Window size: 5 seconds
120
+ Workers: 4
121
+ Press Ctrl+C to stop gracefully
122
+
123
+ Generating event stream for 30 seconds at 10 events/second...
124
+
125
+ Events: 300 | Processed: 298 | Rate: 9.93 e/s | Window: 47 | Avg Time: 0.12 ms
126
+
127
+ Generated 300 events
128
+
129
+ Stopping Stream Processor...
130
+
131
+ ============================================================
132
+ FINAL SUMMARY
133
+ ============================================================
134
+ Total Events: 300
135
+ Processed: 300
136
+ Errors: 0
137
+ Duration: 30.25 seconds
138
+ Average Rate: 9.92 events/second
139
+ ============================================================
140
+ ----
141
+
142
+ === Example 2: High Throughput
143
+
144
+ [source,bash]
145
+ ----
146
+ $ ruby stream_processor.rb -w 8 -r 100 -d 10
147
+
148
+ Starting Stream Processor...
149
+ Window size: 5 seconds
150
+ Workers: 8
151
+
152
+ Events: 1000 | Processed: 998 | Rate: 99.8 e/s | Window: 492 | Avg Time: 0.08 ms
153
+
154
+ Generated 1000 events
155
+
156
+ FINAL SUMMARY
157
+ ============================================================
158
+ Total Events: 1000
159
+ Processed: 1000
160
+ Duration: 10.05 seconds
161
+ Average Rate: 99.50 events/second
162
+ ----
163
+
164
+ === Example 3: Graceful Shutdown
165
+
166
+ Press Ctrl+C during processing:
167
+
168
+ [source,bash]
169
+ ----
170
+ $ ruby stream_processor.rb
171
+
172
+ Events: 150 | Processed: 148 | Rate: 10.1 e/s | Window: 48 | Avg Time: 0.10 ms
173
+ ^C
174
+ Stopping Stream Processor...
175
+
176
+ FINAL SUMMARY
177
+ ============================================================
178
+ Total Events: 150
179
+ Processed: 150
180
+ Duration: 14.87 seconds
181
+ Average Rate: 10.09 events/second
182
+ ----
183
+
184
+ == Architecture
185
+
186
+ The processor consists of:
187
+
188
+ * **Event**: Work item representing a stream event
189
+ * **EventProcessorWorker**: Worker that processes events
190
+ * **StreamProcessor**: Main processor managing the continuous server
191
+ * **EventGenerator**: Test utility for generating event streams
192
+
193
+ == Testing
194
+
195
+ Run the test suite:
196
+
197
+ [source,bash]
198
+ ----
199
+ bundle exec rspec spec/examples/stream_processor_spec.rb
200
+ ----
201
+
202
+ == See Also
203
+
204
+ * link:../../README.adoc[Fractor Main Documentation]
205
+ * link:../../docs/continuous-mode.adoc[Continuous Mode Guide]
206
+ * link:../log_analyzer/README.adoc[Log Analyzer Example]