smart_message 0.0.13 → 0.0.17
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/.gitignore +1 -0
- data/CHANGELOG.md +184 -0
- data/Gemfile.lock +6 -6
- data/README.md +75 -25
- data/docs/guides/transport-selection.md +361 -0
- data/docs/index.md +2 -0
- data/docs/reference/transports.md +78 -29
- data/docs/transports/file-transport.md +535 -0
- data/docs/transports/memory-transport.md +2 -1
- data/docs/transports/multi-transport.md +484 -0
- data/docs/transports/redis-transport.md +1 -1
- data/docs/transports/stdout-transport.md +580 -0
- data/examples/file/00_run_all_file_demos.rb +260 -0
- data/examples/file/01_basic_file_transport_demo.rb +237 -0
- data/examples/file/02_fifo_transport_demo.rb +289 -0
- data/examples/file/03_file_watching_demo.rb +332 -0
- data/examples/file/04_multi_transport_file_demo.rb +432 -0
- data/examples/file/README.md +257 -0
- data/examples/memory/00_run_all_demos.rb +317 -0
- data/examples/memory/01_message_deduplication_demo.rb +18 -30
- data/examples/memory/02_dead_letter_queue_demo.rb +9 -9
- data/examples/memory/03_point_to_point_orders.rb +3 -3
- data/examples/memory/04_publish_subscribe_events.rb +15 -15
- data/examples/memory/05_many_to_many_chat.rb +19 -19
- data/examples/memory/06_stdout_publish_only.rb +118 -0
- data/examples/memory/07_proc_handlers_demo.rb +13 -13
- data/examples/memory/08_custom_logger_demo.rb +136 -136
- data/examples/memory/09_error_handling_demo.rb +7 -7
- data/examples/memory/10_entity_addressing_basic.rb +25 -25
- data/examples/memory/11_entity_addressing_with_filtering.rb +32 -32
- data/examples/memory/12_regex_filtering_microservices.rb +10 -10
- data/examples/memory/14_global_configuration_demo.rb +12 -12
- data/examples/memory/README.md +34 -17
- data/examples/memory/log/demo_app.log.2 +100 -0
- data/examples/multi_transport_example.rb +114 -0
- data/examples/redis/01_smart_home_iot_demo.rb +20 -20
- data/examples/utilities/box_it.rb +12 -0
- data/examples/utilities/doing.rb +19 -0
- data/examples/utilities/temp.md +28 -0
- data/lib/smart_message/base.rb +5 -7
- data/lib/smart_message/errors.rb +3 -0
- data/lib/smart_message/header.rb +1 -1
- data/lib/smart_message/logger/default.rb +1 -1
- data/lib/smart_message/messaging.rb +36 -6
- data/lib/smart_message/plugins.rb +46 -4
- data/lib/smart_message/serializer/base.rb +1 -1
- data/lib/smart_message/serializer.rb +3 -2
- data/lib/smart_message/subscription.rb +18 -20
- data/lib/smart_message/transport/async_publish_queue.rb +284 -0
- data/lib/smart_message/transport/fifo_operations.rb +264 -0
- data/lib/smart_message/transport/file_operations.rb +232 -0
- data/lib/smart_message/transport/file_transport.rb +152 -0
- data/lib/smart_message/transport/file_watching.rb +72 -0
- data/lib/smart_message/transport/partitioned_files.rb +46 -0
- data/lib/smart_message/transport/stdout_transport.rb +7 -81
- data/lib/smart_message/transport/stdout_transport.rb.backup +88 -0
- data/lib/smart_message/version.rb +1 -1
- data/mkdocs.yml +4 -5
- metadata +26 -10
- data/ideas/README.md +0 -41
- data/ideas/agents.md +0 -1001
- data/ideas/database_transport.md +0 -980
- data/ideas/improvement.md +0 -359
- data/ideas/meshage.md +0 -1788
- data/ideas/message_discovery.md +0 -178
- data/ideas/message_schema.md +0 -1381
- data/lib/smart_message/wrapper.rb.bak +0 -132
- /data/examples/memory/{06_pretty_print_demo.rb → 16_pretty_print_demo.rb} +0 -0
@@ -32,12 +32,11 @@ module SmartMessage
|
|
32
32
|
|
33
33
|
# Call a registered proc handler
|
34
34
|
# @param handler_id [String] The handler identifier
|
35
|
-
|
36
|
-
def call_proc_handler(handler_id, wrapper)
|
35
|
+
def call_proc_handler(handler_id, message)
|
37
36
|
handler_proc = class_variable_get(:@@proc_handlers)[handler_id]
|
38
37
|
return unless handler_proc
|
39
38
|
|
40
|
-
handler_proc.call(
|
39
|
+
handler_proc.call(message)
|
41
40
|
end
|
42
41
|
|
43
42
|
# Remove a proc handler from the registry
|
@@ -61,8 +60,8 @@ module SmartMessage
|
|
61
60
|
# an exception.
|
62
61
|
#
|
63
62
|
# @param process_method [String, Proc, nil] The processing method:
|
64
|
-
# - String: Method name like "MyService.handle_message"
|
65
|
-
# - Proc: A proc/lambda that accepts (
|
63
|
+
# - String: Method name like "MyService.handle_message"
|
64
|
+
# - Proc: A proc/lambda that accepts (message)
|
66
65
|
# - nil: Uses default "MessageClass.process" method
|
67
66
|
# @param broadcast [Boolean, nil] Filter for broadcast messages (to: nil)
|
68
67
|
# @param to [String, Array, nil] Filter for messages directed to specific entities
|
@@ -70,26 +69,25 @@ module SmartMessage
|
|
70
69
|
# @param block [Proc] Alternative way to pass a processing block
|
71
70
|
# @return [String] The identifier used for this subscription
|
72
71
|
#
|
73
|
-
# @example Using default handler
|
72
|
+
# @example Using default handler
|
74
73
|
# MyMessage.subscribe
|
75
74
|
#
|
76
75
|
# @example Using custom method name with filtering
|
77
76
|
# MyMessage.subscribe("MyService.handle_message", from: ['order-service'])
|
78
77
|
#
|
79
78
|
# @example Using a block with broadcast filtering
|
80
|
-
# MyMessage.subscribe(broadcast: true) do |
|
81
|
-
#
|
82
|
-
# puts "Received broadcast: #{data}"
|
79
|
+
# MyMessage.subscribe(broadcast: true) do |message|
|
80
|
+
# puts "Received broadcast: #{message.content}"
|
83
81
|
# end
|
84
82
|
#
|
85
83
|
# @example Entity-specific filtering (receives only messages from payment service)
|
86
84
|
# MyMessage.subscribe("OrderService.process", from: ['payment'])
|
87
85
|
#
|
88
|
-
# @example Explicit to filter
|
86
|
+
# @example Explicit to filter
|
89
87
|
# MyMessage.subscribe("AdminService.handle", to: 'admin', broadcast: false)
|
90
88
|
def subscribe(process_method = nil, broadcast: nil, to: nil, from: nil, &block)
|
91
89
|
message_class = whoami
|
92
|
-
|
90
|
+
|
93
91
|
# Handle different parameter types
|
94
92
|
if block_given?
|
95
93
|
# Block was passed - use it as the handler
|
@@ -107,11 +105,11 @@ module SmartMessage
|
|
107
105
|
|
108
106
|
# Subscriber identity is derived from the process method (handler)
|
109
107
|
# This ensures each handler gets its own DDQ scope per message class
|
110
|
-
|
108
|
+
|
111
109
|
# Normalize string filters to arrays
|
112
110
|
to_filter = normalize_filter_value(to)
|
113
111
|
from_filter = normalize_filter_value(from)
|
114
|
-
|
112
|
+
|
115
113
|
# Create filter options (no explicit subscriber identity needed)
|
116
114
|
filter_options = {
|
117
115
|
broadcast: broadcast,
|
@@ -121,16 +119,16 @@ module SmartMessage
|
|
121
119
|
|
122
120
|
# Add proper logging
|
123
121
|
logger = SmartMessage::Logger.default
|
124
|
-
|
122
|
+
|
125
123
|
begin
|
126
124
|
raise Errors::TransportNotConfigured if transport_missing?
|
127
125
|
transport.subscribe(message_class, process_method, filter_options)
|
128
|
-
|
126
|
+
|
129
127
|
# Log successful subscription
|
130
128
|
handler_desc = block_given? || process_method.respond_to?(:call) ? " with block/proc handler" : ""
|
131
129
|
logger.info { "[SmartMessage] Subscribed: #{self.name}#{handler_desc}" }
|
132
130
|
logger.debug { "[SmartMessage::Subscription] Subscribed #{message_class} with filters: #{filter_options}" }
|
133
|
-
|
131
|
+
|
134
132
|
process_method
|
135
133
|
rescue => e
|
136
134
|
logger.error { "[SmartMessage] Error in message subscription: #{e.class.name} - #{e.message}" }
|
@@ -148,16 +146,16 @@ module SmartMessage
|
|
148
146
|
process_method = message_class + '.process' if process_method.nil?
|
149
147
|
# Add proper logging
|
150
148
|
logger = SmartMessage::Logger.default
|
151
|
-
|
149
|
+
|
152
150
|
begin
|
153
151
|
if transport_configured?
|
154
152
|
transport.unsubscribe(message_class, process_method)
|
155
|
-
|
153
|
+
|
156
154
|
# If this was a proc handler, clean it up from the registry
|
157
155
|
if proc_handler?(process_method)
|
158
156
|
unregister_proc_handler(process_method)
|
159
157
|
end
|
160
|
-
|
158
|
+
|
161
159
|
# Log successful unsubscription
|
162
160
|
logger.info { "[SmartMessage] Unsubscribed: #{self.name}" }
|
163
161
|
logger.debug { "[SmartMessage::Subscription] Unsubscribed #{message_class} from #{process_method}" }
|
@@ -193,4 +191,4 @@ module SmartMessage
|
|
193
191
|
end
|
194
192
|
end
|
195
193
|
end
|
196
|
-
end
|
194
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
# lib/smart_message/transport/async_publish_queue.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
module SmartMessage
|
8
|
+
module Transport
|
9
|
+
# Module for asynchronous publishing queue.
|
10
|
+
module AsyncPublishQueue
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def logger
|
15
|
+
# Ensure we have a proper logger, not just an IO object
|
16
|
+
if @logger && @logger.respond_to?(:error) && @logger.respond_to?(:info) && @logger.respond_to?(:warn)
|
17
|
+
return @logger
|
18
|
+
end
|
19
|
+
@logger = SmartMessage::Logger.default
|
20
|
+
end
|
21
|
+
|
22
|
+
public
|
23
|
+
def configure_async_publishing
|
24
|
+
return unless @options[:async]
|
25
|
+
|
26
|
+
# Ensure file configuration is set up first
|
27
|
+
configure_file_output if respond_to?(:configure_file_output)
|
28
|
+
|
29
|
+
@publish_queue = @options[:max_queue] ? SizedQueue.new(@options[:max_queue]) : Queue.new
|
30
|
+
@queue_stats = { queued: 0, processed: 0, failed: 0, dropped: 0, blocked_count: 0, max_queue_size_reached: 0 }
|
31
|
+
@last_warning_time = nil
|
32
|
+
|
33
|
+
start_publish_worker
|
34
|
+
start_queue_monitoring if @options[:enable_queue_monitoring]
|
35
|
+
end
|
36
|
+
|
37
|
+
def async_publish(message_class, serialized_message)
|
38
|
+
check_queue_warning
|
39
|
+
|
40
|
+
if queue_full?
|
41
|
+
handle_queue_overflow(message_class, serialized_message)
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
|
45
|
+
@publish_queue << {
|
46
|
+
message_class: message_class,
|
47
|
+
serialized_message: serialized_message,
|
48
|
+
timestamp: Time.now,
|
49
|
+
retry_count: 0
|
50
|
+
}
|
51
|
+
@queue_stats[:queued] += 1
|
52
|
+
true
|
53
|
+
rescue => e
|
54
|
+
begin
|
55
|
+
logger.error { "[FileTransport] Error queuing message: #{e.message}" }
|
56
|
+
rescue
|
57
|
+
# Fallback if logger is not available
|
58
|
+
end
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def queue_full?
|
63
|
+
@publish_queue.is_a?(SizedQueue) && @publish_queue.size >= @options[:max_queue]
|
64
|
+
end
|
65
|
+
|
66
|
+
def queue_usage_percentage
|
67
|
+
return 0 unless @publish_queue.is_a?(SizedQueue)
|
68
|
+
(@publish_queue.size.to_f / @options[:max_queue] * 100).round(1)
|
69
|
+
end
|
70
|
+
|
71
|
+
def publish_stats
|
72
|
+
return {} unless @options[:async]
|
73
|
+
@queue_stats.merge(
|
74
|
+
current_size: @publish_queue.size,
|
75
|
+
worker_alive: @publish_worker_thread&.alive? || false
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def start_publish_worker
|
82
|
+
@publish_worker_thread = Thread.new do
|
83
|
+
Thread.current.name = "FileTransport-Publisher"
|
84
|
+
|
85
|
+
loop do
|
86
|
+
begin
|
87
|
+
message_data = Timeout.timeout(@options[:worker_timeout] || 5) { @publish_queue.pop }
|
88
|
+
process_queued_message(message_data)
|
89
|
+
rescue Timeout::Error
|
90
|
+
next
|
91
|
+
rescue => e
|
92
|
+
begin
|
93
|
+
logger.error { "[FileTransport] Publish worker error: #{e.message}" }
|
94
|
+
rescue
|
95
|
+
# Fallback if logger is not available
|
96
|
+
end
|
97
|
+
sleep 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def process_queued_message(message_data)
|
104
|
+
success = do_sync_publish(message_data[:message_class], message_data[:serialized_message])
|
105
|
+
if success
|
106
|
+
@queue_stats[:processed] += 1
|
107
|
+
else
|
108
|
+
handle_publish_failure(message_data)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def do_sync_publish(message_class, serialized_message)
|
113
|
+
begin
|
114
|
+
if @options[:file_type] == :fifo
|
115
|
+
write_to_fifo(serialized_message)
|
116
|
+
else
|
117
|
+
write_to_file(serialized_message)
|
118
|
+
end
|
119
|
+
true
|
120
|
+
rescue => e
|
121
|
+
begin
|
122
|
+
logger.error { "[FileTransport] Sync write error: #{e.message}" }
|
123
|
+
rescue
|
124
|
+
# Fallback if logger is not available
|
125
|
+
end
|
126
|
+
false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def handle_publish_failure(message_data)
|
131
|
+
retry_count = message_data[:retry_count] + 1
|
132
|
+
max_retries = @options[:max_retries] || 3
|
133
|
+
|
134
|
+
if retry_count <= max_retries
|
135
|
+
sleep_time = [2 ** retry_count, @options[:max_retry_delay] || 30].min
|
136
|
+
Thread.new do
|
137
|
+
sleep sleep_time
|
138
|
+
message_data[:retry_count] = retry_count
|
139
|
+
@publish_queue << message_data
|
140
|
+
end
|
141
|
+
logger.warn { "[FileTransport] Retrying message publish (attempt #{retry_count}/#{max_retries})" }
|
142
|
+
else
|
143
|
+
@queue_stats[:failed] += 1
|
144
|
+
send_to_dead_letter_queue(message_data) if defined?(SmartMessage::DeadLetterQueue)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def handle_queue_overflow(message_class, serialized_message)
|
149
|
+
case @options[:queue_overflow_strategy]
|
150
|
+
when :drop_newest
|
151
|
+
@queue_stats[:dropped] += 1
|
152
|
+
logger.warn { "[FileTransport] Dropping newest message - queue full (#{@publish_queue.size}/#{@options[:max_queue]})" }
|
153
|
+
when :drop_oldest
|
154
|
+
begin
|
155
|
+
dropped_message = @publish_queue.pop(true)
|
156
|
+
@publish_queue << {
|
157
|
+
message_class: message_class,
|
158
|
+
serialized_message: serialized_message,
|
159
|
+
timestamp: Time.now,
|
160
|
+
retry_count: 0
|
161
|
+
}
|
162
|
+
@queue_stats[:dropped] += 1
|
163
|
+
logger.warn { "[FileTransport] Dropped oldest message to make room for new message" }
|
164
|
+
send_to_dead_letter_queue(dropped_message) if @options[:send_dropped_to_dlq]
|
165
|
+
rescue ThreadError
|
166
|
+
@queue_stats[:dropped] += 1
|
167
|
+
logger.error { "[FileTransport] Queue overflow but queue appears empty - dropping message" }
|
168
|
+
end
|
169
|
+
when :block
|
170
|
+
@queue_stats[:blocked_count] += 1
|
171
|
+
start_time = Time.now
|
172
|
+
logger.warn { "[FileTransport] Queue full (#{@publish_queue.size}/#{@options[:max_queue]}) - blocking until space available" }
|
173
|
+
|
174
|
+
@publish_queue << {
|
175
|
+
message_class: message_class,
|
176
|
+
serialized_message: serialized_message,
|
177
|
+
timestamp: Time.now,
|
178
|
+
retry_count: 0
|
179
|
+
}
|
180
|
+
logger.info { "[FileTransport] Queue space available after #{(Time.now - start_time).round(2)}s - message queued" }
|
181
|
+
@queue_stats[:queued] += 1
|
182
|
+
return true
|
183
|
+
else
|
184
|
+
@queue_stats[:dropped] += 1
|
185
|
+
logger.error { "[FileTransport] Unknown overflow strategy '#{@options[:queue_overflow_strategy]}', dropping message" }
|
186
|
+
end
|
187
|
+
false
|
188
|
+
end
|
189
|
+
|
190
|
+
def send_to_dead_letter_queue(message_data)
|
191
|
+
SmartMessage::DeadLetterQueue.default.enqueue(
|
192
|
+
message_data[:message_class],
|
193
|
+
message_data[:serialized_message],
|
194
|
+
error: "Failed async publish after #{@options[:max_retries]} retries",
|
195
|
+
transport: self.class.name,
|
196
|
+
retry_count: message_data[:retry_count]
|
197
|
+
)
|
198
|
+
rescue => e
|
199
|
+
begin
|
200
|
+
logger.error { "[FileTransport] Failed to send to DLQ: #{e.message}" }
|
201
|
+
rescue
|
202
|
+
# Fallback if logger is not available
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def check_queue_warning
|
207
|
+
return unless @options[:queue_warning_threshold]
|
208
|
+
return if @options[:max_queue] == 0
|
209
|
+
|
210
|
+
usage = queue_usage_percentage
|
211
|
+
threshold = @options[:queue_warning_threshold] * 100
|
212
|
+
|
213
|
+
if usage >= threshold
|
214
|
+
now = Time.now
|
215
|
+
if @last_warning_time.nil? || (now - @last_warning_time) > 60
|
216
|
+
logger.warn { "[FileTransport] Publish queue is #{usage}% full (#{@publish_queue.size}/#{@options[:max_queue]})" }
|
217
|
+
@last_warning_time = now
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def start_queue_monitoring
|
223
|
+
@queue_monitoring_thread = Thread.new do
|
224
|
+
Thread.current.name = "FileTransport-QueueMonitor"
|
225
|
+
|
226
|
+
loop do
|
227
|
+
sleep 30
|
228
|
+
|
229
|
+
begin
|
230
|
+
stats = publish_stats
|
231
|
+
usage = queue_usage_percentage
|
232
|
+
|
233
|
+
logger.info do
|
234
|
+
"[FileTransport] Queue stats: #{stats[:current_size]} messages " \
|
235
|
+
"(#{usage}% full), processed: #{stats[:processed]}, " \
|
236
|
+
"failed: #{stats[:failed]}, worker: #{stats[:worker_alive] ? 'alive' : 'dead'}"
|
237
|
+
end
|
238
|
+
rescue => e
|
239
|
+
logger.error { "[FileTransport] Queue monitoring error: #{e.message}" }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def stop_async_publishing
|
246
|
+
return unless @publish_worker_thread
|
247
|
+
|
248
|
+
@publish_worker_thread.kill
|
249
|
+
@publish_worker_thread.join(@options[:shutdown_timeout] || 10)
|
250
|
+
|
251
|
+
if @options[:drain_queue_on_shutdown]
|
252
|
+
drain_publish_queue
|
253
|
+
end
|
254
|
+
|
255
|
+
@publish_worker_thread = nil
|
256
|
+
@queue_monitoring_thread&.kill
|
257
|
+
@queue_monitoring_thread&.join(5)
|
258
|
+
end
|
259
|
+
|
260
|
+
def drain_publish_queue
|
261
|
+
begin
|
262
|
+
logger.info { "[FileTransport] Draining #{@publish_queue.size} queued messages" }
|
263
|
+
rescue
|
264
|
+
# Fallback if logger is not available
|
265
|
+
end
|
266
|
+
|
267
|
+
until @publish_queue.empty?
|
268
|
+
begin
|
269
|
+
message_data = @publish_queue.pop(true)
|
270
|
+
process_queued_message(message_data)
|
271
|
+
rescue ThreadError
|
272
|
+
break
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
begin
|
277
|
+
logger.info { "[FileTransport] Queue drained" }
|
278
|
+
rescue
|
279
|
+
# Fallback if logger is not available
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
# lib/smart_message/transport/fifo_operations.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'rbconfig'
|
6
|
+
|
7
|
+
module SmartMessage
|
8
|
+
module Transport
|
9
|
+
# Module for FIFO operations, with cross-platform considerations.
|
10
|
+
module FifoOperations
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def logger
|
15
|
+
# Ensure we have a proper logger, not just an IO object
|
16
|
+
if @logger && @logger.respond_to?(:error) && @logger.respond_to?(:info) && @logger.respond_to?(:warn)
|
17
|
+
return @logger
|
18
|
+
end
|
19
|
+
@logger = SmartMessage::Logger.default
|
20
|
+
end
|
21
|
+
|
22
|
+
public
|
23
|
+
def configure_fifo
|
24
|
+
unless platform_supports_fifo?
|
25
|
+
begin
|
26
|
+
logger.warn { "[FileTransport] FIFO not supported, falling back to regular file" }
|
27
|
+
rescue
|
28
|
+
# Fallback if logger is not available
|
29
|
+
end
|
30
|
+
@options[:file_type] = :regular
|
31
|
+
return configure_file_output
|
32
|
+
end
|
33
|
+
|
34
|
+
create_named_pipe if @options[:create_fifo]
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_named_pipe
|
38
|
+
case RbConfig::CONFIG['host_os']
|
39
|
+
when /mswin|mingw|cygwin/
|
40
|
+
create_windows_named_pipe
|
41
|
+
else
|
42
|
+
File.mkfifo(@options[:file_path], @options[:fifo_permissions] || 0644)
|
43
|
+
begin
|
44
|
+
logger.info { "[FileTransport] Created FIFO: #{@options[:file_path]}" }
|
45
|
+
rescue
|
46
|
+
# Fallback if logger is not available
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue NotImplementedError
|
50
|
+
begin
|
51
|
+
logger.error { "[FileTransport] Named pipes not supported on this platform" }
|
52
|
+
rescue
|
53
|
+
# Fallback if logger is not available
|
54
|
+
end
|
55
|
+
raise
|
56
|
+
rescue => e
|
57
|
+
begin
|
58
|
+
logger.error { "[FileTransport] Failed to create FIFO: #{e.message}" }
|
59
|
+
rescue
|
60
|
+
# Fallback if logger is not available
|
61
|
+
end
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_windows_named_pipe
|
66
|
+
require 'win32/pipe'
|
67
|
+
pipe_name = "\\\\.\\pipe\\#{File.basename(@options[:file_path])}"
|
68
|
+
@windows_pipe_server = Win32::Pipe::Server.new(pipe_name)
|
69
|
+
begin
|
70
|
+
logger.info { "[FileTransport] Created Windows named pipe: #{pipe_name}" }
|
71
|
+
rescue
|
72
|
+
# Fallback if logger is not available
|
73
|
+
end
|
74
|
+
rescue LoadError
|
75
|
+
raise "Windows named pipes require win32-pipe gem: gem install win32-pipe"
|
76
|
+
rescue => e
|
77
|
+
begin
|
78
|
+
logger.error { "[FileTransport] Failed to create Windows named pipe: #{e.message}" }
|
79
|
+
rescue
|
80
|
+
# Fallback if logger is not available
|
81
|
+
end
|
82
|
+
raise
|
83
|
+
end
|
84
|
+
|
85
|
+
def platform_supports_fifo?
|
86
|
+
case RbConfig::CONFIG['host_os']
|
87
|
+
when /mswin|mingw|cygwin/
|
88
|
+
defined?(Win32::Pipe)
|
89
|
+
else
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def write_to_fifo(serialized_message)
|
95
|
+
handle = open_fifo_for_writing
|
96
|
+
unless handle
|
97
|
+
handle_fifo_write_failure(serialized_message)
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
|
101
|
+
content = prepare_file_content(serialized_message)
|
102
|
+
handle.write(content)
|
103
|
+
handle.flush
|
104
|
+
true
|
105
|
+
rescue Errno::EPIPE
|
106
|
+
begin
|
107
|
+
logger.warn { "[FileTransport] FIFO reader disconnected" }
|
108
|
+
rescue
|
109
|
+
# Fallback if logger is not available
|
110
|
+
end
|
111
|
+
handle_fifo_write_failure(serialized_message)
|
112
|
+
false
|
113
|
+
rescue => e
|
114
|
+
begin
|
115
|
+
logger.error { "[FileTransport] FIFO write error: #{e.message}" }
|
116
|
+
rescue
|
117
|
+
# Fallback if logger is not available
|
118
|
+
end
|
119
|
+
handle_fifo_write_failure(serialized_message)
|
120
|
+
false
|
121
|
+
ensure
|
122
|
+
handle&.close
|
123
|
+
end
|
124
|
+
|
125
|
+
def open_fifo_for_writing
|
126
|
+
mode = @options[:fifo_mode] == :non_blocking ? File::WRONLY | File::NONBLOCK : 'w'
|
127
|
+
File.open(@options[:file_path], mode)
|
128
|
+
rescue Errno::ENXIO, Errno::ENOENT
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def handle_fifo_write_failure(serialized_message)
|
133
|
+
if @options[:fallback_transport]
|
134
|
+
begin
|
135
|
+
@options[:fallback_transport].do_publish(@current_message_class, serialized_message)
|
136
|
+
begin
|
137
|
+
logger.info { "[FileTransport] Message sent to fallback transport" }
|
138
|
+
rescue
|
139
|
+
# Fallback if logger is not available
|
140
|
+
end
|
141
|
+
rescue => e
|
142
|
+
begin
|
143
|
+
logger.error { "[FileTransport] Fallback transport failed: #{e.message}" }
|
144
|
+
rescue
|
145
|
+
# Fallback if logger is not available
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def start_fifo_reader(message_class, process_method, filter_options)
|
152
|
+
case @options[:subscription_mode]
|
153
|
+
when :fifo_blocking
|
154
|
+
start_blocking_fifo_reader(message_class, process_method)
|
155
|
+
when :fifo_select
|
156
|
+
start_select_fifo_reader(message_class, process_method)
|
157
|
+
when :fifo_polling
|
158
|
+
start_polling_fifo_reader(message_class, process_method)
|
159
|
+
else
|
160
|
+
begin
|
161
|
+
logger.warn { "[FileTransport] Invalid FIFO subscription mode: #{@options[:subscription_mode]}" }
|
162
|
+
rescue
|
163
|
+
# Fallback if logger is not available
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def start_blocking_fifo_reader(message_class, process_method)
|
169
|
+
@fifo_reader_thread = Thread.new do
|
170
|
+
Thread.current.name = "FileTransport-FifoReader"
|
171
|
+
loop do
|
172
|
+
begin
|
173
|
+
File.open(@options[:file_path], 'r') do |fifo|
|
174
|
+
while line = fifo.gets
|
175
|
+
next if line.strip.empty?
|
176
|
+
receive(message_class, line.strip)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
rescue => e
|
180
|
+
begin
|
181
|
+
logger.error { "[FileTransport] FIFO reader error: #{e.message}" }
|
182
|
+
rescue
|
183
|
+
# Fallback if logger is not available
|
184
|
+
end
|
185
|
+
sleep 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def start_select_fifo_reader(message_class, process_method)
|
192
|
+
@fifo_select_thread = Thread.new do
|
193
|
+
Thread.current.name = "FileTransport-FifoSelect"
|
194
|
+
fifo = File.open(@options[:file_path], File::RDONLY | File::NONBLOCK)
|
195
|
+
|
196
|
+
loop do
|
197
|
+
ready = IO.select([fifo], nil, nil, 1.0)
|
198
|
+
if ready
|
199
|
+
begin
|
200
|
+
while line = fifo.gets
|
201
|
+
next if line.strip.empty?
|
202
|
+
receive(message_class, line.strip)
|
203
|
+
end
|
204
|
+
rescue IO::WaitReadable
|
205
|
+
next
|
206
|
+
rescue => e
|
207
|
+
begin
|
208
|
+
logger.error { "[FileTransport] FIFO select error: #{e.message}" }
|
209
|
+
rescue
|
210
|
+
# Fallback if logger is not available
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
rescue => e
|
216
|
+
begin
|
217
|
+
logger.error { "[FileTransport] FIFO select thread error: #{e.message}" }
|
218
|
+
rescue
|
219
|
+
# Fallback if logger is not available
|
220
|
+
end
|
221
|
+
ensure
|
222
|
+
fifo&.close
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def start_polling_fifo_reader(message_class, process_method)
|
227
|
+
@fifo_reader_thread = Thread.new do
|
228
|
+
Thread.current.name = "FileTransport-FifoPoller"
|
229
|
+
loop do
|
230
|
+
begin
|
231
|
+
File.open(@options[:file_path], File::RDONLY | File::NONBLOCK) do |fifo|
|
232
|
+
while line = fifo.gets
|
233
|
+
next if line.strip.empty?
|
234
|
+
receive(message_class, line.strip)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
rescue Errno::EAGAIN
|
238
|
+
sleep(@options[:poll_interval] || 1.0)
|
239
|
+
rescue => e
|
240
|
+
begin
|
241
|
+
logger.error { "[FileTransport] FIFO polling error: #{e.message}" }
|
242
|
+
rescue
|
243
|
+
# Fallback if logger is not available
|
244
|
+
end
|
245
|
+
sleep 1
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def stop_fifo_operations
|
252
|
+
@fifo_reader_thread&.kill
|
253
|
+
@fifo_reader_thread&.join(2)
|
254
|
+
@fifo_select_thread&.kill
|
255
|
+
@fifo_select_thread&.join(2)
|
256
|
+
@fifo_handle&.close
|
257
|
+
end
|
258
|
+
|
259
|
+
def fifo_active?
|
260
|
+
@fifo_reader_thread&.alive? || @fifo_select_thread&.alive?
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|