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
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../../lib/fractor"
5
+ require "json"
6
+ require "time"
7
+
8
+ # Event data structure
9
+ class Event < Fractor::Work
10
+ def initialize(type:, data:, timestamp: Time.now)
11
+ super({
12
+ type: type,
13
+ data: data,
14
+ timestamp: timestamp
15
+ })
16
+ end
17
+
18
+ def type
19
+ input[:type]
20
+ end
21
+
22
+ def data
23
+ input[:data]
24
+ end
25
+
26
+ def timestamp
27
+ input[:timestamp]
28
+ end
29
+
30
+ def to_s
31
+ "Event(#{type}, #{timestamp.iso8601})"
32
+ end
33
+ end
34
+
35
+ # Worker for processing events
36
+ class EventProcessorWorker < Fractor::Worker
37
+ def process(work)
38
+ return nil unless work.is_a?(Event)
39
+
40
+ # Process event
41
+ {
42
+ type: work.type,
43
+ data: work.data,
44
+ timestamp: work.timestamp,
45
+ processed_at: Time.now,
46
+ processing_time: (Time.now - work.timestamp) * 1000 # ms
47
+ }
48
+ end
49
+ end
50
+
51
+ # Real-time stream processor (simplified for testing)
52
+ class StreamProcessor
53
+ attr_reader :processed_count, :error_count, :window_size, :metrics
54
+
55
+ def initialize(window_size: 5, num_workers: 4)
56
+ @window_size = window_size
57
+ @num_workers = num_workers
58
+ @processed_count = 0
59
+ @error_count = 0
60
+ @events_in_window = []
61
+ @metrics = {
62
+ total_events: 0,
63
+ events_per_second: 0.0,
64
+ average_processing_time: 0.0,
65
+ current_window_count: 0
66
+ }
67
+ @start_time = Time.now
68
+ @mutex = Mutex.new
69
+ @supervisor = nil
70
+ @running = false
71
+ end
72
+
73
+ def start
74
+ puts "Starting Stream Processor..."
75
+ puts "Window size: #{@window_size} seconds"
76
+ puts "Workers: #{@num_workers}"
77
+ puts
78
+
79
+ @supervisor = Fractor::Supervisor.new(
80
+ worker_pools: [
81
+ { worker_class: EventProcessorWorker, num_workers: @num_workers }
82
+ ]
83
+ )
84
+
85
+ @running = true
86
+ @start_time = Time.now
87
+
88
+ self
89
+ end
90
+
91
+ def add_event(event)
92
+ return unless @supervisor && @running
93
+
94
+ @supervisor.add_work_item(event)
95
+
96
+ @mutex.synchronize do
97
+ @metrics[:total_events] += 1
98
+ end
99
+ end
100
+
101
+ def process_events
102
+ return unless @supervisor && @running
103
+
104
+ @supervisor.run
105
+
106
+ results_obj = @supervisor.results
107
+ all_results = results_obj.results + results_obj.errors
108
+
109
+ all_results.each do |work_result|
110
+ result = work_result.respond_to?(:result) ? work_result.result : work_result
111
+
112
+ next unless result.is_a?(Hash)
113
+
114
+ process_result(result)
115
+ end
116
+ end
117
+
118
+ def stop
119
+ puts "\nStopping Stream Processor..."
120
+ @running = false
121
+ print_final_summary
122
+ end
123
+
124
+ private
125
+
126
+ def process_result(result)
127
+ @mutex.synchronize do
128
+ @processed_count += 1
129
+
130
+ # Add to current window
131
+ @events_in_window << result
132
+
133
+ # Remove events outside window
134
+ cutoff_time = Time.now - @window_size
135
+ @events_in_window.reject! do |r|
136
+ r[:processed_at] < cutoff_time
137
+ end
138
+
139
+ # Update metrics
140
+ @metrics[:current_window_count] = @events_in_window.size
141
+
142
+ if @processed_count > 0
143
+ elapsed = Time.now - @start_time
144
+ @metrics[:events_per_second] = @processed_count / elapsed
145
+
146
+ total_time = @events_in_window.sum { |r| r[:processing_time] }
147
+ @metrics[:average_processing_time] =
148
+ @events_in_window.empty? ? 0.0 : total_time / @events_in_window.size
149
+ end
150
+ end
151
+ end
152
+
153
+ def print_metrics
154
+ @mutex.synchronize do
155
+ print "\r"
156
+ print "Events: #{@metrics[:total_events]} | "
157
+ print "Processed: #{@processed_count} | "
158
+ print "Rate: #{@metrics[:events_per_second].round(2)} e/s | "
159
+ print "Window: #{@metrics[:current_window_count]} | "
160
+ print "Avg Time: #{@metrics[:average_processing_time].round(2)} ms"
161
+ $stdout.flush
162
+ end
163
+ end
164
+
165
+ def print_final_summary
166
+ puts "\n"
167
+ puts "=" * 60
168
+ puts "FINAL SUMMARY"
169
+ puts "=" * 60
170
+ puts format("Total Events: %d", @metrics[:total_events])
171
+ puts format("Processed: %d", @processed_count)
172
+ puts format("Errors: %d", @error_count)
173
+ elapsed = Time.now - @start_time
174
+ puts format("Duration: %.2f seconds", elapsed)
175
+ rate = @processed_count > 0 ? @processed_count / elapsed : 0.0
176
+ puts format("Average Rate: %.2f events/second", rate)
177
+ puts "=" * 60
178
+ end
179
+ end
180
+
181
+ # Event generator for testing
182
+ class EventGenerator
183
+ def self.generate_stream(processor, duration: 10, rate: 10)
184
+ puts "Generating event stream for #{duration} seconds at #{rate} events/second..."
185
+
186
+ start_time = Time.now
187
+ event_count = 0
188
+
189
+ while (Time.now - start_time) < duration
190
+ event = Event.new(
191
+ type: [:click, :view, :purchase, :signup].sample,
192
+ data: {
193
+ user_id: rand(1..1000),
194
+ value: rand(1.0..100.0).round(2)
195
+ }
196
+ )
197
+
198
+ processor.add_event(event)
199
+ event_count += 1
200
+
201
+ # Control rate
202
+ sleep(1.0 / rate)
203
+ end
204
+
205
+ puts "\nGenerated #{event_count} events"
206
+ end
207
+ end
208
+
209
+ # Run example if executed directly
210
+ if __FILE__ == $PROGRAM_NAME
211
+ require "optparse"
212
+
213
+ options = {
214
+ workers: 4,
215
+ window_size: 5,
216
+ duration: 30,
217
+ rate: 10
218
+ }
219
+
220
+ OptionParser.new do |opts|
221
+ opts.banner = "Usage: stream_processor.rb [options]"
222
+
223
+ opts.on("-w", "--workers NUM", Integer, "Number of workers (default: 4)") do |n|
224
+ options[:workers] = n
225
+ end
226
+
227
+ opts.on("--window SIZE", Integer, "Window size in seconds (default: 5)") do |s|
228
+ options[:window_size] = s
229
+ end
230
+
231
+ opts.on("-d", "--duration SECONDS", Integer, "Test duration (default: 30)") do |d|
232
+ options[:duration] = d
233
+ end
234
+
235
+ opts.on("-r", "--rate NUM", Integer, "Events per second (default: 10)") do |r|
236
+ options[:rate] = r
237
+ end
238
+
239
+ opts.on("-h", "--help", "Show this message") do
240
+ puts opts
241
+ exit
242
+ end
243
+ end.parse!
244
+
245
+ # Create processor
246
+ processor = StreamProcessor.new(
247
+ window_size: options[:window_size],
248
+ num_workers: options[:workers]
249
+ )
250
+
251
+ processor.start
252
+
253
+ # Handle graceful shutdown
254
+ trap("INT") do
255
+ processor.stop
256
+ exit
257
+ end
258
+
259
+ # Generate test events in background
260
+ generator_thread = Thread.new do
261
+ EventGenerator.generate_stream(
262
+ processor,
263
+ duration: options[:duration],
264
+ rate: options[:rate]
265
+ )
266
+ end
267
+
268
+ # Process events periodically
269
+ metrics_thread = Thread.new do
270
+ while processor.instance_variable_get(:@running)
271
+ sleep(1)
272
+ processor.send(:print_metrics)
273
+ end
274
+ end
275
+
276
+ # Wait for generation to complete
277
+ generator_thread.join
278
+
279
+ # Process remaining events
280
+ processor.process_events
281
+
282
+ metrics_thread.kill
283
+ processor.stop
284
+ end