fractor 0.1.3 → 0.1.6

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 (34) 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 +154 -48
  5. data/README.adoc +1371 -317
  6. data/examples/auto_detection/README.adoc +52 -0
  7. data/examples/auto_detection/auto_detection.rb +170 -0
  8. data/examples/continuous_chat_common/message_protocol.rb +53 -0
  9. data/examples/continuous_chat_fractor/README.adoc +217 -0
  10. data/examples/continuous_chat_fractor/chat_client.rb +303 -0
  11. data/examples/continuous_chat_fractor/chat_common.rb +83 -0
  12. data/examples/continuous_chat_fractor/chat_server.rb +167 -0
  13. data/examples/continuous_chat_fractor/simulate.rb +345 -0
  14. data/examples/continuous_chat_server/README.adoc +135 -0
  15. data/examples/continuous_chat_server/chat_client.rb +303 -0
  16. data/examples/continuous_chat_server/chat_server.rb +359 -0
  17. data/examples/continuous_chat_server/simulate.rb +343 -0
  18. data/examples/hierarchical_hasher/hierarchical_hasher.rb +12 -8
  19. data/examples/multi_work_type/multi_work_type.rb +30 -29
  20. data/examples/pipeline_processing/pipeline_processing.rb +15 -15
  21. data/examples/producer_subscriber/producer_subscriber.rb +20 -16
  22. data/examples/scatter_gather/scatter_gather.rb +29 -28
  23. data/examples/simple/sample.rb +38 -6
  24. data/examples/specialized_workers/specialized_workers.rb +44 -37
  25. data/lib/fractor/continuous_server.rb +188 -0
  26. data/lib/fractor/result_aggregator.rb +1 -1
  27. data/lib/fractor/supervisor.rb +291 -108
  28. data/lib/fractor/version.rb +1 -1
  29. data/lib/fractor/work_queue.rb +68 -0
  30. data/lib/fractor/work_result.rb +1 -1
  31. data/lib/fractor/worker.rb +2 -1
  32. data/lib/fractor/wrapped_ractor.rb +12 -2
  33. data/lib/fractor.rb +2 -0
  34. metadata +17 -2
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Fractor
6
+ # High-level wrapper for running Fractor in continuous mode.
7
+ # Handles threading, signal handling, and results processing automatically.
8
+ class ContinuousServer
9
+ attr_reader :supervisor, :work_queue
10
+
11
+ # Initialize a continuous server
12
+ # @param worker_pools [Array<Hash>] Worker pool configurations
13
+ # @param work_queue [WorkQueue, nil] Optional work queue to auto-register
14
+ # @param log_file [String, nil] Optional log file path
15
+ def initialize(worker_pools:, work_queue: nil, log_file: nil)
16
+ @worker_pools = worker_pools
17
+ @work_queue = work_queue
18
+ @log_file_path = log_file
19
+ @log_file = nil
20
+ @result_callbacks = []
21
+ @error_callbacks = []
22
+ @supervisor = nil
23
+ @supervisor_thread = nil
24
+ @results_thread = nil
25
+ @running = false
26
+ end
27
+
28
+ # Register a callback for successful results
29
+ # @yield [WorkResult] The successful result
30
+ def on_result(&block)
31
+ @result_callbacks << block
32
+ end
33
+
34
+ # Register a callback for errors
35
+ # @yield [WorkResult] The error result
36
+ def on_error(&block)
37
+ @error_callbacks << block
38
+ end
39
+
40
+ # Start the server and block until shutdown
41
+ # This method handles:
42
+ # - Opening log file if specified
43
+ # - Creating and starting supervisor
44
+ # - Starting results processing thread
45
+ # - Setting up signal handlers
46
+ # - Blocking until shutdown signal received
47
+ def run
48
+ setup_log_file
49
+ setup_supervisor
50
+ start_supervisor_thread
51
+ start_results_thread
52
+
53
+ log_message("Continuous server started")
54
+ log_message("Press Ctrl+C to stop")
55
+
56
+ begin
57
+ # Block until shutdown
58
+ @supervisor_thread&.join
59
+ rescue Interrupt
60
+ log_message("Interrupt received, shutting down...")
61
+ ensure
62
+ cleanup
63
+ end
64
+ end
65
+
66
+ # Stop the server programmatically
67
+ def stop
68
+ return unless @running
69
+
70
+ log_message("Stopping continuous server...")
71
+ @running = false
72
+
73
+ @supervisor&.stop
74
+
75
+ # Wait for threads to finish
76
+ [@supervisor_thread, @results_thread].compact.each do |thread|
77
+ thread.join(2) if thread.alive?
78
+ end
79
+
80
+ log_message("Continuous server stopped")
81
+ end
82
+
83
+ private
84
+
85
+ def setup_log_file
86
+ return unless @log_file_path
87
+
88
+ FileUtils.mkdir_p(File.dirname(@log_file_path))
89
+ @log_file = File.open(@log_file_path, "w")
90
+ end
91
+
92
+ def setup_supervisor
93
+ @supervisor = Supervisor.new(
94
+ worker_pools: @worker_pools,
95
+ continuous_mode: true,
96
+ )
97
+
98
+ # Auto-register work queue if provided
99
+ if @work_queue
100
+ @work_queue.register_with_supervisor(@supervisor)
101
+ log_message(
102
+ "Work queue registered with supervisor (batch size: 10)",
103
+ )
104
+ end
105
+ end
106
+
107
+ def start_supervisor_thread
108
+ @running = true
109
+ @supervisor_thread = Thread.new do
110
+ @supervisor.run
111
+ rescue StandardError => e
112
+ log_message("Supervisor error: #{e.message}")
113
+ log_message(e.backtrace.join("\n")) if ENV["FRACTOR_DEBUG"]
114
+ end
115
+
116
+ # Give supervisor time to start up
117
+ sleep(0.1)
118
+ end
119
+
120
+ def start_results_thread
121
+ @results_thread = Thread.new do
122
+ log_message("Results processing thread started")
123
+ process_results_loop
124
+ rescue StandardError => e
125
+ log_message("Results thread error: #{e.message}")
126
+ log_message(e.backtrace.join("\n")) if ENV["FRACTOR_DEBUG"]
127
+ end
128
+ end
129
+
130
+ def process_results_loop
131
+ while @running
132
+ sleep(0.05)
133
+
134
+ process_successful_results
135
+ process_error_results
136
+ end
137
+ log_message("Results processing thread stopped")
138
+ end
139
+
140
+ def process_successful_results
141
+ loop do
142
+ result = @supervisor.results.results.shift
143
+ break unless result
144
+
145
+ @result_callbacks.each do |callback|
146
+ callback.call(result)
147
+ rescue StandardError => e
148
+ log_message("Error in result callback: #{e.message}")
149
+ end
150
+ end
151
+ end
152
+
153
+ def process_error_results
154
+ loop do
155
+ error_result = @supervisor.results.errors.shift
156
+ break unless error_result
157
+
158
+ @error_callbacks.each do |callback|
159
+ callback.call(error_result)
160
+ rescue StandardError => e
161
+ log_message("Error in error callback: #{e.message}")
162
+ end
163
+ end
164
+ end
165
+
166
+ def cleanup
167
+ @running = false
168
+
169
+ # Close log file if open
170
+ if @log_file && !@log_file.closed?
171
+ @log_file.close
172
+ @log_file = nil
173
+ end
174
+ end
175
+
176
+ def log_message(message)
177
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")
178
+ log_entry = "[#{timestamp}] #{message}"
179
+
180
+ if @log_file && !@log_file.closed?
181
+ @log_file.puts(log_entry)
182
+ @log_file.flush
183
+ end
184
+
185
+ puts log_entry
186
+ end
187
+ end
188
+ end
@@ -36,7 +36,7 @@ module Fractor
36
36
  def inspect
37
37
  {
38
38
  results: @results.map(&:inspect),
39
- errors: @errors.map(&:inspect)
39
+ errors: @errors.map(&:inspect),
40
40
  }
41
41
  end
42
42
  end