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.
- checksums.yaml +4 -4
- data/.rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-main-ci-rubocop-yml +552 -0
- data/.rubocop.yml +14 -8
- data/.rubocop_todo.yml +154 -48
- data/README.adoc +1371 -317
- data/examples/auto_detection/README.adoc +52 -0
- data/examples/auto_detection/auto_detection.rb +170 -0
- data/examples/continuous_chat_common/message_protocol.rb +53 -0
- data/examples/continuous_chat_fractor/README.adoc +217 -0
- data/examples/continuous_chat_fractor/chat_client.rb +303 -0
- data/examples/continuous_chat_fractor/chat_common.rb +83 -0
- data/examples/continuous_chat_fractor/chat_server.rb +167 -0
- data/examples/continuous_chat_fractor/simulate.rb +345 -0
- data/examples/continuous_chat_server/README.adoc +135 -0
- data/examples/continuous_chat_server/chat_client.rb +303 -0
- data/examples/continuous_chat_server/chat_server.rb +359 -0
- data/examples/continuous_chat_server/simulate.rb +343 -0
- data/examples/hierarchical_hasher/hierarchical_hasher.rb +12 -8
- data/examples/multi_work_type/multi_work_type.rb +30 -29
- data/examples/pipeline_processing/pipeline_processing.rb +15 -15
- data/examples/producer_subscriber/producer_subscriber.rb +20 -16
- data/examples/scatter_gather/scatter_gather.rb +29 -28
- data/examples/simple/sample.rb +38 -6
- data/examples/specialized_workers/specialized_workers.rb +44 -37
- data/lib/fractor/continuous_server.rb +188 -0
- data/lib/fractor/result_aggregator.rb +1 -1
- data/lib/fractor/supervisor.rb +291 -108
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/work_queue.rb +68 -0
- data/lib/fractor/work_result.rb +1 -1
- data/lib/fractor/worker.rb +2 -1
- data/lib/fractor/wrapped_ractor.rb +12 -2
- data/lib/fractor.rb +2 -0
- 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
|