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
@@ -9,7 +9,7 @@ module PipelineProcessing
9
9
  super({
10
10
  data: data,
11
11
  stage: stage,
12
- metadata: metadata
12
+ metadata: metadata,
13
13
  })
14
14
  end
15
15
 
@@ -29,7 +29,7 @@ module PipelineProcessing
29
29
  "MediaWork: stage=#{stage}, metadata=#{metadata}, data_size=#{begin
30
30
  data.bytesize
31
31
  rescue StandardError
32
- "unknown"
32
+ 'unknown'
33
33
  end}"
34
34
  end
35
35
  end
@@ -46,7 +46,7 @@ module PipelineProcessing
46
46
  else
47
47
  return Fractor::WorkResult.new(
48
48
  error: "Unknown stage: #{work.stage}",
49
- work: work
49
+ work: work,
50
50
  )
51
51
  end
52
52
 
@@ -57,8 +57,8 @@ module PipelineProcessing
57
57
 
58
58
  # Update metadata with processing information
59
59
  updated_metadata = work.metadata.merge(
60
- "#{work.stage}_completed" => true,
61
- "#{work.stage}_time" => Time.now.to_s
60
+ "#{work.stage}_completed".to_sym => true,
61
+ "#{work.stage}_time".to_sym => Time.now.to_s,
62
62
  )
63
63
 
64
64
  # Return the result with next stage information
@@ -67,9 +67,9 @@ module PipelineProcessing
67
67
  processed_data: result,
68
68
  current_stage: work.stage,
69
69
  next_stage: next_stage,
70
- metadata: updated_metadata
70
+ metadata: updated_metadata,
71
71
  },
72
- work: work
72
+ work: work,
73
73
  )
74
74
  end
75
75
 
@@ -95,7 +95,7 @@ module PipelineProcessing
95
95
  sleep(rand(0.01..0.05)) # Simulate processing time
96
96
  tags = %w[landscape portrait nature urban abstract]
97
97
  selected_tags = tags.sample(rand(1..3))
98
- "Tagged image: #{work.data} (tags: #{selected_tags.join(", ")})"
98
+ "Tagged image: #{work.data} (tags: #{selected_tags.join(', ')})"
99
99
  end
100
100
  end
101
101
 
@@ -106,8 +106,8 @@ module PipelineProcessing
106
106
  def initialize(worker_count = 4)
107
107
  @supervisor = Fractor::Supervisor.new(
108
108
  worker_pools: [
109
- { worker_class: PipelineWorker, num_workers: worker_count }
110
- ]
109
+ { worker_class: PipelineWorker, num_workers: worker_count },
110
+ ],
111
111
  )
112
112
 
113
113
  # Register callback to handle pipeline stage transitions
@@ -119,7 +119,7 @@ module PipelineProcessing
119
119
  new_work = MediaWork.new(
120
120
  result.result[:processed_data],
121
121
  next_stage,
122
- result.result[:metadata]
122
+ result.result[:metadata],
123
123
  )
124
124
  @supervisor.add_work_item(new_work)
125
125
  end
@@ -127,7 +127,7 @@ module PipelineProcessing
127
127
 
128
128
  @results = {
129
129
  completed: [],
130
- in_progress: []
130
+ in_progress: [],
131
131
  }
132
132
  end
133
133
 
@@ -137,7 +137,7 @@ module PipelineProcessing
137
137
  MediaWork.new(
138
138
  image,
139
139
  :resize,
140
- { original_filename: image, started_at: Time.now.to_s }
140
+ { original_filename: image, started_at: Time.now.to_s },
141
141
  )
142
142
  end
143
143
 
@@ -159,7 +159,7 @@ module PipelineProcessing
159
159
  total_images: images.size,
160
160
  completed: @results[:completed].size,
161
161
  in_progress: @results[:in_progress].size,
162
- results: @results[:completed]
162
+ results: @results[:completed],
163
163
  }
164
164
  end
165
165
  end
@@ -182,7 +182,7 @@ if __FILE__ == $PROGRAM_NAME
182
182
  "mountains.png",
183
183
  "beach.jpg",
184
184
  "city_skyline.jpg",
185
- "forest.png"
185
+ "forest.png",
186
186
  ]
187
187
 
188
188
  worker_count = 4
@@ -205,7 +205,7 @@ if __FILE__ == $PROGRAM_NAME
205
205
  puts "Image #{index + 1}: #{image_result[:processed_data]}"
206
206
  puts " Processing path:"
207
207
  image_result[:metadata].each do |key, value|
208
- next unless key.to_s.end_with?("_completed") || key.to_s.end_with?("_time")
208
+ next unless key.to_s.end_with?("_completed", "_time")
209
209
 
210
210
  puts " #{key}: #{value}"
211
211
  end
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/fractor"
5
+
6
+ # Example demonstrating priority-based work processing
7
+ # This shows how to use PriorityWork and PriorityWorkQueue for
8
+ # processing tasks based on their priority levels
9
+
10
+ # Define a worker that processes priority work
11
+ class PriorityWorker < Fractor::Worker
12
+ def process(work)
13
+ # Simulate work processing
14
+ sleep 0.1
15
+
16
+ result = "Processed #{work.input[:task]} " \
17
+ "(priority: #{work.priority}, age: #{work.age.round(2)}s)"
18
+
19
+ Fractor::WorkResult.new(result: result, work: work)
20
+ end
21
+ end
22
+
23
+ puts "=" * 60
24
+ puts "Priority Work Example"
25
+ puts "=" * 60
26
+ puts
27
+
28
+ # Example 1: Basic Priority Queue
29
+ puts "Example 1: Basic Priority Ordering"
30
+ puts "-" * 60
31
+
32
+ queue = Fractor::PriorityWorkQueue.new
33
+
34
+ # Add work items with different priorities
35
+ queue.push(Fractor::PriorityWork.new({ task: "Background report" }, priority: :background))
36
+ queue.push(Fractor::PriorityWork.new({ task: "Critical bug fix" }, priority: :critical))
37
+ queue.push(Fractor::PriorityWork.new({ task: "Normal feature" }, priority: :normal))
38
+ queue.push(Fractor::PriorityWork.new({ task: "High priority task" }, priority: :high))
39
+ queue.push(Fractor::PriorityWork.new({ task: "Low priority cleanup" }, priority: :low))
40
+
41
+ puts "Queue statistics:"
42
+ stats = queue.stats
43
+ puts " Total items: #{stats[:total]}"
44
+ puts " By priority: #{stats[:by_priority]}"
45
+ puts
46
+
47
+ puts "Processing in priority order:"
48
+ 5.times do
49
+ work = queue.pop_non_blocking
50
+ puts " #{work.input[:task]} (#{work.priority})"
51
+ end
52
+ puts
53
+
54
+ # Example 2: Priority Aging
55
+ puts "Example 2: Priority Aging (Preventing Starvation)"
56
+ puts "-" * 60
57
+
58
+ aged_queue = Fractor::PriorityWorkQueue.new(
59
+ aging_enabled: true,
60
+ aging_threshold: 2 # 2 seconds
61
+ )
62
+
63
+ # Add a low-priority item first
64
+ aged_queue.push(Fractor::PriorityWork.new(
65
+ { task: "Old low-priority task" },
66
+ priority: :low
67
+ ))
68
+
69
+ puts "Added low-priority task at #{Time.now.strftime('%H:%M:%S')}"
70
+ puts "Waiting 3 seconds to let it age..."
71
+ sleep 3
72
+
73
+ # Add high-priority items after the low-priority one has aged
74
+ aged_queue.push(Fractor::PriorityWork.new(
75
+ { task: "New high-priority task" },
76
+ priority: :high
77
+ ))
78
+
79
+ puts "Added high-priority task at #{Time.now.strftime('%H:%M:%S')}"
80
+ puts
81
+
82
+ puts "Processing order with aging enabled:"
83
+ 2.times do
84
+ work = aged_queue.pop_non_blocking
85
+ puts " #{work.input[:task]} " \
86
+ "(priority: #{work.priority}, age: #{work.age.round(1)}s)"
87
+ end
88
+ puts "Note: The aged low-priority task was processed first!"
89
+ puts
90
+
91
+ # Example 3: Using with Supervisor
92
+ puts "Example 3: Integration with Supervisor"
93
+ puts "-" * 60
94
+
95
+ priority_queue = Fractor::PriorityWorkQueue.new
96
+
97
+ # Add mixed priority work
98
+ [
99
+ { task: "Process payment", priority: :critical },
100
+ { task: "Send email", priority: :normal },
101
+ { task: "Generate report", priority: :low },
102
+ { task: "Update inventory", priority: :high },
103
+ { task: "Cleanup cache", priority: :background }
104
+ ].each do |item|
105
+ priority_queue.push(Fractor::PriorityWork.new(item, priority: item[:priority]))
106
+ end
107
+
108
+ # Create supervisor with priority queue
109
+ supervisor = Fractor::Supervisor.new(
110
+ work_queue: priority_queue,
111
+ worker_pools: [
112
+ { worker_class: PriorityWorker, num_workers: 2 }
113
+ ]
114
+ )
115
+
116
+ puts "Processing #{priority_queue.size} tasks with 2 workers..."
117
+ supervisor.start
118
+
119
+ # Wait for all work to complete
120
+ sleep 1 until priority_queue.empty?
121
+
122
+ supervisor.shutdown
123
+ results = supervisor.results
124
+
125
+ puts "\nResults (in completion order):"
126
+ results.each_with_index do |result, i|
127
+ puts " #{i + 1}. #{result.result}"
128
+ end
129
+ puts
130
+
131
+ # Example 4: Queue Statistics
132
+ puts "Example 4: Monitoring Queue Statistics"
133
+ puts "-" * 60
134
+
135
+ stats_queue = Fractor::PriorityWorkQueue.new
136
+
137
+ # Add various priority items
138
+ 10.times do |i|
139
+ priority = [:critical, :high, :normal, :low, :background].sample
140
+ stats_queue.push(Fractor::PriorityWork.new({ id: i }, priority: priority))
141
+ end
142
+
143
+ stats = stats_queue.stats
144
+ puts "Queue statistics:"
145
+ puts " Total items: #{stats[:total]}"
146
+ puts " Closed: #{stats[:closed]}"
147
+ puts " Items by priority:"
148
+ stats[:by_priority].each do |priority, count|
149
+ puts " #{priority}: #{count}"
150
+ end
151
+ puts
152
+
153
+ puts "=" * 60
154
+ puts "Priority Work Examples Complete"
155
+ puts "=" * 60