smart_message 0.0.12 → 0.0.16

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +155 -1
  4. data/Gemfile.lock +6 -6
  5. data/README.md +71 -25
  6. data/docs/core-concepts/architecture.md +5 -10
  7. data/docs/getting-started/examples.md +0 -12
  8. data/docs/getting-started/quick-start.md +4 -9
  9. data/docs/index.md +6 -4
  10. data/docs/reference/serializers.md +160 -488
  11. data/docs/reference/transports.md +47 -146
  12. data/docs/transports/memory-transport.md +2 -1
  13. data/docs/transports/multi-transport.md +484 -0
  14. data/docs/transports/redis-transport-comparison.md +215 -350
  15. data/docs/transports/redis-transport.md +3 -22
  16. data/examples/README.md +6 -9
  17. data/examples/city_scenario/README.md +1 -1
  18. data/examples/city_scenario/messages/emergency_911_message.rb +0 -1
  19. data/examples/city_scenario/messages/emergency_resolved_message.rb +0 -1
  20. data/examples/city_scenario/messages/fire_dispatch_message.rb +0 -1
  21. data/examples/city_scenario/messages/fire_emergency_message.rb +0 -1
  22. data/examples/city_scenario/messages/health_check_message.rb +0 -1
  23. data/examples/city_scenario/messages/health_status_message.rb +0 -1
  24. data/examples/city_scenario/messages/police_dispatch_message.rb +0 -1
  25. data/examples/city_scenario/messages/silent_alarm_message.rb +0 -1
  26. data/examples/file/00_run_all_file_demos.rb +260 -0
  27. data/examples/file/01_basic_file_transport_demo.rb +237 -0
  28. data/examples/file/02_fifo_transport_demo.rb +289 -0
  29. data/examples/file/03_file_watching_demo.rb +332 -0
  30. data/examples/file/04_multi_transport_file_demo.rb +432 -0
  31. data/examples/file/README.md +257 -0
  32. data/examples/memory/00_run_all_demos.rb +317 -0
  33. data/examples/memory/01_message_deduplication_demo.rb +18 -32
  34. data/examples/memory/02_dead_letter_queue_demo.rb +9 -12
  35. data/examples/memory/03_point_to_point_orders.rb +3 -5
  36. data/examples/memory/04_publish_subscribe_events.rb +15 -16
  37. data/examples/memory/05_many_to_many_chat.rb +19 -22
  38. data/examples/memory/06_stdout_publish_only.rb +145 -0
  39. data/examples/memory/07_proc_handlers_demo.rb +13 -14
  40. data/examples/memory/08_custom_logger_demo.rb +136 -140
  41. data/examples/memory/09_error_handling_demo.rb +7 -10
  42. data/examples/memory/10_entity_addressing_basic.rb +25 -31
  43. data/examples/memory/11_entity_addressing_with_filtering.rb +32 -36
  44. data/examples/memory/12_regex_filtering_microservices.rb +10 -11
  45. data/examples/memory/13_header_block_configuration.rb +0 -5
  46. data/examples/memory/14_global_configuration_demo.rb +12 -14
  47. data/examples/memory/15_logger_demo.rb +0 -1
  48. data/examples/memory/README.md +37 -20
  49. data/examples/memory/log/demo_app.log.1 +100 -0
  50. data/examples/memory/log/demo_app.log.2 +100 -0
  51. data/examples/multi_transport_example.rb +114 -0
  52. data/examples/redis/01_smart_home_iot_demo.rb +20 -24
  53. data/examples/redis/README.md +0 -2
  54. data/examples/utilities/box_it.rb +12 -0
  55. data/examples/utilities/doing.rb +19 -0
  56. data/examples/utilities/temp.md +28 -0
  57. data/lib/smart_message/base.rb +24 -17
  58. data/lib/smart_message/configuration.rb +2 -23
  59. data/lib/smart_message/dead_letter_queue.rb +1 -1
  60. data/lib/smart_message/errors.rb +3 -0
  61. data/lib/smart_message/header.rb +1 -1
  62. data/lib/smart_message/logger/default.rb +1 -1
  63. data/lib/smart_message/messaging.rb +37 -66
  64. data/lib/smart_message/plugins.rb +42 -41
  65. data/lib/smart_message/serializer/base.rb +1 -1
  66. data/lib/smart_message/serializer.rb +3 -2
  67. data/lib/smart_message/subscription.rb +18 -20
  68. data/lib/smart_message/transport/async_publish_queue.rb +284 -0
  69. data/lib/smart_message/transport/base.rb +42 -8
  70. data/lib/smart_message/transport/fifo_operations.rb +264 -0
  71. data/lib/smart_message/transport/file_operations.rb +200 -0
  72. data/lib/smart_message/transport/file_transport.rb +149 -0
  73. data/lib/smart_message/transport/file_watching.rb +72 -0
  74. data/lib/smart_message/transport/memory_transport.rb +23 -4
  75. data/lib/smart_message/transport/partitioned_files.rb +46 -0
  76. data/lib/smart_message/transport/redis_transport.rb +11 -0
  77. data/lib/smart_message/transport/registry.rb +0 -1
  78. data/lib/smart_message/transport/stdout_transport.rb +73 -41
  79. data/lib/smart_message/transport/stdout_transport.rb.backup +88 -0
  80. data/lib/smart_message/transport.rb +0 -1
  81. data/lib/smart_message/version.rb +1 -1
  82. metadata +25 -37
  83. data/docs/guides/redis-queue-getting-started.md +0 -697
  84. data/docs/guides/redis-queue-patterns.md +0 -889
  85. data/docs/guides/redis-queue-production.md +0 -1091
  86. data/docs/transports/redis-enhanced-transport.md +0 -524
  87. data/docs/transports/redis-queue-transport.md +0 -1304
  88. data/examples/redis_enhanced/README.md +0 -319
  89. data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +0 -233
  90. data/examples/redis_enhanced/enhanced_02_fluent_api.rb +0 -331
  91. data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +0 -281
  92. data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +0 -419
  93. data/examples/redis_queue/01_basic_messaging.rb +0 -221
  94. data/examples/redis_queue/01_comprehensive_examples.rb +0 -508
  95. data/examples/redis_queue/02_pattern_routing.rb +0 -405
  96. data/examples/redis_queue/03_fluent_api.rb +0 -422
  97. data/examples/redis_queue/04_load_balancing.rb +0 -486
  98. data/examples/redis_queue/05_microservices.rb +0 -735
  99. data/examples/redis_queue/06_emergency_alerts.rb +0 -777
  100. data/examples/redis_queue/07_queue_management.rb +0 -587
  101. data/examples/redis_queue/README.md +0 -366
  102. data/examples/redis_queue/enhanced_01_basic_patterns.rb +0 -233
  103. data/examples/redis_queue/enhanced_02_fluent_api.rb +0 -331
  104. data/examples/redis_queue/enhanced_03_dual_publishing.rb +0 -281
  105. data/examples/redis_queue/enhanced_04_advanced_routing.rb +0 -419
  106. data/examples/redis_queue/redis_queue_architecture.svg +0 -148
  107. data/ideas/README.md +0 -41
  108. data/ideas/agents.md +0 -1001
  109. data/ideas/database_transport.md +0 -980
  110. data/ideas/improvement.md +0 -359
  111. data/ideas/meshage.md +0 -1788
  112. data/ideas/message_discovery.md +0 -178
  113. data/ideas/message_schema.md +0 -1381
  114. data/lib/smart_message/transport/redis_enhanced_transport.rb +0 -399
  115. data/lib/smart_message/transport/redis_queue_transport.rb +0 -555
  116. data/lib/smart_message/wrapper.rb.bak +0 -132
  117. /data/examples/memory/{06_pretty_print_demo.rb → 16_pretty_print_demo.rb} +0 -0
@@ -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
@@ -11,15 +11,17 @@ module SmartMessage
11
11
  class Base
12
12
  include BreakerMachines::DSL
13
13
 
14
- attr_reader :options, :dispatcher
14
+ attr_reader :options, :dispatcher, :serializer
15
15
 
16
16
  def initialize(**options)
17
17
  @options = default_options.merge(options)
18
18
  @dispatcher = options[:dispatcher] || SmartMessage::Dispatcher.new
19
+ @serializer = options[:serializer] || default_serializer
19
20
  configure
20
21
  configure_transport_circuit_breakers
21
22
 
22
23
  logger.debug { "[SmartMessage::Transport::#{self.class.name.split('::').last}] Initialized with options: #{@options}" }
24
+ logger.debug { "[SmartMessage::Transport::#{self.class.name.split('::').last}] Using serializer: #{@serializer.class.name}" }
23
25
  rescue => e
24
26
  logger&.error { "[SmartMessage] Error in transport initialization: #{e.class.name} - #{e.message}" }
25
27
  raise
@@ -43,10 +45,20 @@ module SmartMessage
43
45
  {}
44
46
  end
45
47
 
48
+ # Default serializer for this transport (override in subclasses)
49
+ def default_serializer
50
+ SmartMessage::Serializer::Json.new
51
+ end
52
+
46
53
  # Publish a message with circuit breaker protection
47
- # @param message_class [String] The message class name (used for channel routing)
48
- # @param serialized_message [String] Complete serialized message content
49
- def publish(message_class, serialized_message)
54
+ # @param message [SmartMessage::Base] The message instance to publish
55
+ def publish(message)
56
+ # Extract routing info from message header
57
+ message_class = message._sm_header.message_class
58
+
59
+ # Serialize the entire message (flat structure with _sm_header)
60
+ serialized_message = encode_message(message)
61
+
50
62
  circuit(:transport_publish).wrap do
51
63
  do_publish(message_class, serialized_message)
52
64
  end
@@ -58,7 +70,7 @@ module SmartMessage
58
70
  raise unless e.is_a?(Hash) && e[:circuit_breaker]
59
71
 
60
72
  # Handle circuit breaker fallback
61
- handle_publish_fallback(e, message_class, serialized_message)
73
+ handle_publish_fallback(e, message._sm_header.message_class, serialized_message)
62
74
  end
63
75
 
64
76
  # Template method for actual publishing (implement in subclasses)
@@ -150,13 +162,32 @@ module SmartMessage
150
162
  end
151
163
  end
152
164
 
165
+ # Encode a message using the transport's serializer
166
+ # @param message [SmartMessage::Base] The message to encode
167
+ # @return [String] The serialized message
168
+ def encode_message(message)
169
+ # Update header with serializer info
170
+ message._sm_header.serializer = @serializer.class.to_s
171
+
172
+ # Serialize the entire message as a flat structure
173
+ @serializer.encode(message.to_hash)
174
+ end
175
+
176
+ # Decode a message using the transport's serializer
177
+ # @param serialized_message [String] The serialized message
178
+ # @return [Hash] The decoded message data
179
+ def decode_message(serialized_message)
180
+ @serializer.decode(serialized_message)
181
+ end
182
+
153
183
  # Receive and route a message (called by transport implementations)
154
184
  # @param message_class [String] The message class name
155
185
  # @param serialized_message [String] The serialized message content
156
186
  protected
157
187
 
158
188
  def receive(message_class, serialized_message)
159
- # Decode the message using the class's configured serializer
189
+ # Decode the message using transport's serializer
190
+ decoded_data = decode_message(serialized_message)
160
191
 
161
192
  # Add defensive check for message_class type
162
193
  unless message_class.respond_to?(:constantize)
@@ -165,10 +196,13 @@ module SmartMessage
165
196
  raise ArgumentError, "message_class must be a String, got #{message_class.class.name}"
166
197
  end
167
198
 
199
+ # Reconstruct the message instance from flat structure
168
200
  message_class_obj = message_class.constantize
169
- decoded_message = message_class_obj.decode(serialized_message)
201
+ # Convert string keys to symbols for keyword arguments
202
+ symbol_data = decoded_data.transform_keys(&:to_sym)
203
+ message = message_class_obj.new(**symbol_data)
170
204
 
171
- @dispatcher.route(decoded_message)
205
+ @dispatcher.route(message)
172
206
  rescue => e
173
207
  logger.error { "[SmartMessage] Error in transport receive: #{e.class.name} - #{e.message}" }
174
208
  logger.error { "[SmartMessage] message_class: #{message_class.inspect} (#{message_class.class.name})" }
@@ -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