smart_message 0.0.13 → 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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +120 -0
- data/Gemfile.lock +3 -3
- data/README.md +71 -25
- data/docs/index.md +2 -0
- data/docs/reference/transports.md +46 -21
- data/docs/transports/memory-transport.md +2 -1
- data/docs/transports/multi-transport.md +484 -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 +145 -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.1 +100 -0
- 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 +200 -0
- data/lib/smart_message/transport/file_transport.rb +149 -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 +50 -36
- data/lib/smart_message/transport/stdout_transport.rb.backup +88 -0
- data/lib/smart_message/version.rb +1 -1
- metadata +24 -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
@@ -30,9 +30,14 @@ module SmartMessage
|
|
30
30
|
def transport(klass_or_instance = nil)
|
31
31
|
if klass_or_instance.nil?
|
32
32
|
# Return instance transport, class transport, or global configuration
|
33
|
-
|
33
|
+
# For backward compatibility, return first transport if array, otherwise single transport
|
34
|
+
transport_value = @transport || self.class.class_variable_get(:@@transport) || SmartMessage::Transport.default
|
35
|
+
transport_value.is_a?(Array) ? transport_value.first : transport_value
|
34
36
|
else
|
35
|
-
|
37
|
+
# Normalize to array for internal consistent handling
|
38
|
+
@transport = Array(klass_or_instance)
|
39
|
+
# Return the original value for backward compatibility with method chaining
|
40
|
+
klass_or_instance
|
36
41
|
end
|
37
42
|
end
|
38
43
|
|
@@ -44,6 +49,22 @@ module SmartMessage
|
|
44
49
|
end
|
45
50
|
def reset_transport; @transport = nil; end
|
46
51
|
|
52
|
+
# Utility methods for working with transport collections
|
53
|
+
def transports
|
54
|
+
# Get the raw transport value (which is internally stored as array)
|
55
|
+
raw_transport = @transport || self.class.class_variable_get(:@@transport) || SmartMessage::Transport.default
|
56
|
+
# Always return as array for consistent handling
|
57
|
+
raw_transport.is_a?(Array) ? raw_transport : Array(raw_transport)
|
58
|
+
end
|
59
|
+
|
60
|
+
def single_transport?
|
61
|
+
transports.length == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def multiple_transports?
|
65
|
+
transports.length > 1
|
66
|
+
end
|
67
|
+
|
47
68
|
module ClassMethods
|
48
69
|
#########################################################
|
49
70
|
## class-level configuration
|
@@ -58,9 +79,14 @@ module SmartMessage
|
|
58
79
|
def transport(klass_or_instance = nil)
|
59
80
|
if klass_or_instance.nil?
|
60
81
|
# Return class-level transport or fall back to global configuration
|
61
|
-
|
82
|
+
# For backward compatibility, return first transport if array, otherwise single transport
|
83
|
+
transport_value = class_variable_get(:@@transport) || SmartMessage::Transport.default
|
84
|
+
transport_value.is_a?(Array) ? transport_value.first : transport_value
|
62
85
|
else
|
63
|
-
|
86
|
+
# Normalize to array for internal consistent handling
|
87
|
+
class_variable_set(:@@transport, Array(klass_or_instance))
|
88
|
+
# Return the original value for backward compatibility with method chaining
|
89
|
+
klass_or_instance
|
64
90
|
end
|
65
91
|
end
|
66
92
|
|
@@ -71,6 +97,22 @@ module SmartMessage
|
|
71
97
|
end
|
72
98
|
def reset_transport; class_variable_set(:@@transport, nil); end
|
73
99
|
|
100
|
+
# Utility methods for working with transport collections
|
101
|
+
def transports
|
102
|
+
# Get the raw transport value (which is internally stored as array)
|
103
|
+
raw_transport = class_variable_get(:@@transport) || SmartMessage::Transport.default
|
104
|
+
# Always return as array for consistent handling
|
105
|
+
raw_transport.is_a?(Array) ? raw_transport : Array(raw_transport)
|
106
|
+
end
|
107
|
+
|
108
|
+
def single_transport?
|
109
|
+
transports.length == 1
|
110
|
+
end
|
111
|
+
|
112
|
+
def multiple_transports?
|
113
|
+
transports.length > 1
|
114
|
+
end
|
115
|
+
|
74
116
|
#########################################################
|
75
117
|
## class-level logger configuration
|
76
118
|
|
@@ -58,7 +58,7 @@ module SmartMessage::Serializer
|
|
58
58
|
|
59
59
|
# Template methods for actual serialization (implement in subclasses)
|
60
60
|
def do_encode(message_instance)
|
61
|
-
# Default implementation: serialize only the payload portion for
|
61
|
+
# Default implementation: serialize only the payload portion for message architecture
|
62
62
|
# Subclasses can override this for specific serialization formats
|
63
63
|
message_hash = message_instance.to_h
|
64
64
|
payload_portion = message_hash[:_sm_payload]
|
@@ -6,8 +6,9 @@ module SmartMessage
|
|
6
6
|
module Serializer
|
7
7
|
class << self
|
8
8
|
def default
|
9
|
-
#
|
10
|
-
|
9
|
+
# Return the framework's default serializer class
|
10
|
+
# Note: Serialization is handled by transports, not messages
|
11
|
+
SmartMessage::Serializer::Json
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end
|
@@ -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
|