eventq_rabbitmq 1.17.0-java

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2ac92d8a90294367f4ee7b8f69034b90f6605d8481d755c8a50712a272acb103
4
+ data.tar.gz: 4fc200d78f706bf3fd57040a4b310b6b7a591ba3337a2114582d2aea814c4a8d
5
+ SHA512:
6
+ metadata.gz: cdc0d278a62aea36ece0309710cc34ee46b7d3229f0d819b15bec7e23b65e128d60ff14d90c11e969b2a5d437771dd3a8608b8d51bd581d23a34502390642cb0
7
+ data.tar.gz: 2710024884afd8230a1762cde151983321b37fe67dfa4c19d450466d72fd3029f24a268ef741fa75daba3364fc8885125d41f3ec7c1d4c691db707f37d6a4892
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "eventq_rabbitmq"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,12 @@
1
+ module EventQ
2
+ module RabbitMq
3
+ class DefaultQueue < Queue
4
+ def initialize
5
+ @name = 'Default'.freeze
6
+ @allow_retry = false
7
+ @max_retry_attempts = 1
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,373 @@
1
+ require 'java'
2
+ java_import java.util.concurrent.Executors
3
+ module EventQ
4
+ module RabbitMq
5
+ class QueueWorker
6
+ include EventQ::WorkerId
7
+
8
+ attr_accessor :is_running
9
+
10
+ def initialize
11
+ @threads = []
12
+ @forks = []
13
+ @is_running = false
14
+
15
+ @retry_exceeded_block = nil
16
+ @on_retry_block = nil
17
+ @on_error_block = nil
18
+ @hash_helper = HashKit::Helper.new
19
+ @serialization_provider_manager = EventQ::SerializationProviders::Manager.new
20
+ @signature_provider_manager = EventQ::SignatureProviders::Manager.new
21
+ @last_gc_flush = Time.now
22
+ @gc_flush_interval = 10
23
+ end
24
+
25
+ def start(queue, options = {}, &block)
26
+
27
+ EventQ.logger.info("[#{self.class}] - Preparing to start listening for messages.")
28
+
29
+ configure(queue, options)
30
+
31
+ raise "[#{self.class}] - Worker is already running." if running?
32
+
33
+ if options[:client] == nil
34
+ raise "[#{self.class}] - :client (QueueClient) must be specified."
35
+ end
36
+
37
+ EventQ.logger.info("[#{self.class}] - Listening for messages.")
38
+ EventQ.logger.debug do
39
+ "[#{self.class} #start] - Listening for messages on queue: #{EventQ.create_queue_name(queue.name)}"
40
+ end
41
+
42
+ start_process(options, queue, block)
43
+
44
+ end
45
+
46
+ def start_process(options, queue, block)
47
+
48
+ @is_running = true
49
+
50
+ %w'INT TERM'.each do |sig|
51
+ Signal.trap(sig) {
52
+ stop
53
+ exit
54
+ }
55
+ end
56
+
57
+ if !options.key?(:durable)
58
+ options[:durable] = true
59
+ end
60
+
61
+ client = options[:client]
62
+ manager = EventQ::RabbitMq::QueueManager.new
63
+ manager.durable = options[:durable]
64
+ @connection = client.get_connection
65
+
66
+ @executor = java.util.concurrent.Executors::newFixedThreadPool @thread_count
67
+
68
+ #loop through each thread count
69
+ @thread_count.times do
70
+
71
+ @executor.execute do
72
+
73
+ #begin the queue loop for this thread
74
+ while true do
75
+
76
+ #check if the worker is still allowed to run and break out of thread loop if not
77
+ unless running?
78
+ break
79
+ end
80
+
81
+ if @executor.is_shutdown
82
+ break
83
+ end
84
+
85
+ has_received_message = false
86
+
87
+ begin
88
+
89
+ channel = @connection.create_channel
90
+
91
+ has_received_message = thread_process_iteration(channel, manager, queue, block)
92
+
93
+ rescue => e
94
+ EventQ.logger.error("An unhandled error occurred. Error: #{e} | Backtrace: #{e.backtrace}")
95
+ call_on_error_block(error: e)
96
+ end
97
+
98
+ if channel != nil && channel.open?
99
+ channel.close
100
+ end
101
+
102
+ gc_flush
103
+
104
+ if !has_received_message
105
+ EventQ.logger.debug { "[#{self.class}] - No message received." }
106
+ if @sleep > 0
107
+ EventQ.logger.debug { "[#{self.class}] - Sleeping for #{@sleep} seconds" }
108
+ sleep(@sleep)
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ if options.key?(:wait) && options[:wait] == true
119
+ while running? do end
120
+ @connection.close if @connection.open?
121
+ end
122
+
123
+ return true
124
+
125
+ end
126
+
127
+ def call_on_error_block(error:, message: nil)
128
+ if @on_error_block
129
+ EventQ.logger.debug { "[#{self.class}] - Executing on_error block." }
130
+ begin
131
+ @on_error_block.call(error, message)
132
+ rescue => e
133
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_error block. Error: #{e}")
134
+ end
135
+ else
136
+ EventQ.logger.debug { "[#{self.class}] - No on_error block specified to execute." }
137
+ end
138
+ end
139
+
140
+ def gc_flush
141
+ if Time.now - last_gc_flush > @gc_flush_interval
142
+ GC.start
143
+ @last_gc_flush = Time.now
144
+ end
145
+ end
146
+
147
+ def last_gc_flush
148
+ @last_gc_flush
149
+ end
150
+
151
+ def thread_process_iteration(channel, manager, queue, block)
152
+
153
+ #get the queue
154
+ q = manager.get_queue(channel, queue)
155
+ retry_exchange = manager.get_retry_exchange(channel, queue)
156
+
157
+ received = false
158
+
159
+ begin
160
+ delivery_info, payload = manager.pop_message(queue: q)
161
+
162
+ #check that message was received
163
+ if payload != nil
164
+ received = true
165
+ begin
166
+ tag_processing_thread
167
+ process_message(payload, queue, channel, retry_exchange, delivery_info, block)
168
+ ensure
169
+ untag_processing_thread
170
+ end
171
+
172
+ end
173
+
174
+ rescue => e
175
+ EventQ.logger.error("[#{self.class}] - An error occurred attempting to process a message. Error: #{e} | Backtrace: #{e.backtrace}")
176
+ call_on_error_block(error: e)
177
+ end
178
+
179
+ return received
180
+ end
181
+
182
+ def stop
183
+ EventQ.logger.info { "[#{self.class}] - Stopping..." }
184
+ @is_running = false
185
+ @executor.shutdown
186
+ if @connection != nil
187
+ @connection.close if @connection.open?
188
+ end
189
+ return true
190
+ end
191
+
192
+ def on_retry_exceeded(&block)
193
+ @retry_exceeded_block = block
194
+ return nil
195
+ end
196
+
197
+ def on_retry(&block)
198
+ @on_retry_block = block
199
+ return nil
200
+ end
201
+
202
+ def on_error(&block)
203
+ @on_error_block = block
204
+ return nil
205
+ end
206
+
207
+ def running?
208
+ return @is_running
209
+ end
210
+
211
+ def deserialize_message(payload)
212
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
213
+ return provider.deserialize(payload)
214
+ end
215
+
216
+ def serialize_message(msg)
217
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
218
+ return provider.serialize(msg)
219
+ end
220
+
221
+ def call_on_retry_exceeded_block(message)
222
+ if @retry_exceeded_block != nil
223
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry_exceeded block." }
224
+ begin
225
+ @retry_exceeded_block.call(message)
226
+ rescue => e
227
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry_exceeded block. Error: #{e}")
228
+ end
229
+ else
230
+ EventQ.logger.debug { "[#{self.class}] - No on_retry_exceeded block specified." }
231
+ end
232
+ end
233
+
234
+ def call_on_retry_block(message)
235
+ if @on_retry_block
236
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry block." }
237
+ begin
238
+ @on_retry_block.call(message, abort)
239
+ rescue => e
240
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry block. Error: #{e}")
241
+ end
242
+ else
243
+ EventQ.logger.debug { "[#{self.class}] - No on_retry block specified." }
244
+ end
245
+ end
246
+
247
+ def reject_message(channel, message, delivery_tag, retry_exchange, queue, abort)
248
+
249
+ EventQ.logger.info("[#{self.class}] - Message rejected removing from queue.")
250
+ #reject the message to remove from queue
251
+ channel.reject(delivery_tag, false)
252
+
253
+ #check if the message retry limit has been exceeded
254
+ if message.retry_attempts >= queue.max_retry_attempts
255
+
256
+ EventQ.logger.info("[#{self.class}] - Message retry attempt limit exceeded. Msg: #{serialize_message(message)}")
257
+
258
+ call_on_retry_exceeded_block(message)
259
+
260
+ #check if the message is allowed to be retried
261
+ elsif queue.allow_retry
262
+
263
+ EventQ.logger.debug { "[#{self.class}] - Incrementing retry attempts count." }
264
+ message.retry_attempts += 1
265
+
266
+ if queue.allow_retry_back_off == true
267
+ EventQ.logger.debug { "[#{self.class}] - Calculating message back off retry delay. Attempts: #{message.retry_attempts} * Retry Delay: #{queue.retry_delay}" }
268
+ message_ttl = message.retry_attempts * queue.retry_delay
269
+ if (message.retry_attempts * queue.retry_delay) > queue.max_retry_delay
270
+ EventQ.logger.debug { "[#{self.class}] - Max message back off retry delay reached." }
271
+ message_ttl = queue.max_retry_delay
272
+ end
273
+ else
274
+ EventQ.logger.debug { "[#{self.class}] - Setting fixed retry delay for message." }
275
+ message_ttl = queue.retry_delay
276
+ end
277
+
278
+ EventQ.logger.debug { "[#{self.class}] - Sending message for retry. Message TTL: #{message_ttl}" }
279
+ retry_exchange.publish(serialize_message(message), :expiration => message_ttl)
280
+ EventQ.logger.debug { "[#{self.class}] - Published message to retry exchange." }
281
+
282
+ call_on_retry_block(message)
283
+
284
+ end
285
+
286
+ return true
287
+
288
+ end
289
+
290
+ def configure(queue, options = {})
291
+
292
+ @queue = queue
293
+
294
+ #default thread count
295
+ @thread_count = 4
296
+ if options.key?(:thread_count)
297
+ @thread_count = options[:thread_count]
298
+ end
299
+
300
+ #default sleep time in seconds
301
+ @sleep = 15
302
+ if options.key?(:sleep)
303
+ @sleep = options[:sleep]
304
+ end
305
+
306
+ @fork_count = 1
307
+ if options.key?(:fork_count)
308
+ @fork_count = options[:fork_count]
309
+ end
310
+
311
+ @gc_flush_interval = 10
312
+ if options.key?(:gc_flush_interval)
313
+ @gc_flush_interval = options[:gc_flush_interval]
314
+ end
315
+
316
+ EventQ.logger.info("[#{self.class}] - Configuring. Process Count: #{@fork_count} | Thread Count: #{@thread_count} | Interval Sleep: #{@sleep}.")
317
+
318
+ return true
319
+
320
+ end
321
+
322
+ private
323
+
324
+ def process_message(payload, queue, channel, retry_exchange, delivery_tag, block)
325
+ abort = false
326
+ error = false
327
+ message = deserialize_message(payload)
328
+
329
+ EventQ.logger.info("[#{self.class}] - Message received. Retry Attempts: #{message.retry_attempts}")
330
+
331
+ @signature_provider_manager.validate_signature(message: message, queue: queue)
332
+
333
+ message_args = EventQ::MessageArgs.new(type: message.type,
334
+ retry_attempts: message.retry_attempts,
335
+ context: message.context,
336
+ content_type: message.content_type)
337
+
338
+ if(!EventQ::NonceManager.is_allowed?(message.id))
339
+ EventQ.logger.info("[#{self.class}] - Duplicate Message received. Dropping message.")
340
+ channel.acknowledge(delivery_tag, false)
341
+ return false
342
+ end
343
+
344
+ #begin worker block for queue message
345
+ begin
346
+ block.call(message.content, message_args)
347
+
348
+ if message_args.abort == true
349
+ abort = true
350
+ EventQ.logger.info("[#{self.class}] - Message aborted.")
351
+ else
352
+ #accept the message as processed
353
+ channel.acknowledge(delivery_tag, false)
354
+ EventQ.logger.info("[#{self.class}] - Message acknowledged.")
355
+ end
356
+
357
+ rescue => e
358
+ EventQ.logger.error("[#{self.class}] - An unhandled error happened attempting to process a queue message. Error: #{e} | Backtrace: #{e.backtrace}")
359
+ error = true
360
+ call_on_error_block(error: e, message: message)
361
+ end
362
+
363
+ if error || abort
364
+ EventQ::NonceManager.failed(message.id)
365
+ reject_message(channel, message, delivery_tag, retry_exchange, queue, abort)
366
+ else
367
+ EventQ::NonceManager.complete(message.id)
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+
@@ -0,0 +1,127 @@
1
+ module EventQ
2
+ module RabbitMq
3
+ # Implements a general interface to raise an event
4
+ # EventQ::Amazon::EventQClient is the sister-class which does the same for AWS
5
+ class EventQClient
6
+
7
+ def initialize(options={})
8
+
9
+ if options[:client] == nil
10
+ raise ':client (QueueClient) must be specified.'.freeze
11
+ end
12
+
13
+ @client = options[:client]
14
+ @queue_manager = QueueManager.new
15
+ @event_raised_exchange = EventRaisedExchange.new
16
+ @serialization_manager = EventQ::SerializationProviders::Manager.new
17
+ @signature_manager = EventQ::SignatureProviders::Manager.new
18
+
19
+ #this array is used to record known event types
20
+ @known_event_types = []
21
+ end
22
+
23
+ def registered?(event_type)
24
+ @known_event_types.include?(event_type)
25
+ end
26
+
27
+ def register_event(event_type)
28
+ if registered?(event_type)
29
+ return true
30
+ end
31
+
32
+ @known_event_types << event_type
33
+ true
34
+ end
35
+
36
+ def publish(topic:, event:, context: {})
37
+ raise_event(topic, event, context)
38
+ end
39
+
40
+ def raise_event(event_type, event, context = {})
41
+ register_event(event_type)
42
+
43
+ _event_type = EventQ.create_event_type(event_type)
44
+
45
+ with_connection do |channel|
46
+ exchange = @queue_manager.get_exchange(channel, @event_raised_exchange)
47
+
48
+ message = serialized_message(_event_type, event, context)
49
+
50
+ exchange.publish(message, routing_key: _event_type)
51
+
52
+ EventQ.logger.debug do
53
+ "[#{self.class}] - Raised event to Exchange: #{_event_type} | Message: #{message}."
54
+ end
55
+ end
56
+ end
57
+
58
+ def raise_event_in_queue(event_type, event, queue, delay, context = {})
59
+ register_event(event_type)
60
+
61
+ _event_type = EventQ.create_event_type(event_type)
62
+
63
+ with_connection do |channel|
64
+ exchange = @queue_manager.get_queue_exchange(channel, queue)
65
+
66
+ delay_exchange = @queue_manager.get_delay_exchange(channel, queue, delay)
67
+
68
+ delay_queue = @queue_manager.create_delay_queue(channel, queue, exchange.name, delay)
69
+ delay_queue.bind(delay_exchange, routing_key: _event_type)
70
+
71
+ _queue_name = EventQ.create_queue_name(queue.name)
72
+
73
+ q = channel.queue(_queue_name, durable: @queue_manager.durable)
74
+ q.bind(exchange, routing_key: _event_type)
75
+
76
+ message = serialized_message(_event_type, event, context)
77
+
78
+ delay_exchange.publish(message, routing_key: _event_type)
79
+
80
+ EventQ.logger.debug do
81
+ "[#{self.class}] - Raised event to Exchange: #{_event_type} | Message: #{message} | Delay: #{delay}."
82
+ end
83
+ end
84
+ end
85
+
86
+ def new_message
87
+ EventQ::QueueMessage.new
88
+ end
89
+
90
+ private
91
+
92
+ def with_connection
93
+ connection = @client.get_connection
94
+
95
+ begin
96
+ channel = connection.create_channel
97
+
98
+ yield(channel)
99
+
100
+ ensure
101
+ channel&.close if channel.open?
102
+ connection.close
103
+ end
104
+
105
+ true
106
+ end
107
+
108
+ def serialized_message(event_type, event, context)
109
+ qm = new_message
110
+ qm.content = event
111
+ qm.type = event_type
112
+ qm.context = context
113
+ qm.content_type = event.class.to_s
114
+
115
+ if EventQ::Configuration.signature_secret != nil
116
+ provider = @signature_manager.get_provider(EventQ::Configuration.signature_provider)
117
+ qm.signature = provider.write(message: qm, secret: EventQ::Configuration.signature_secret)
118
+ end
119
+
120
+ serialization_provider = @serialization_manager.get_provider(EventQ::Configuration.serialization_provider)
121
+
122
+ serialization_provider.serialize(qm)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
@@ -0,0 +1,54 @@
1
+ module EventQ
2
+ module RabbitMq
3
+ class QueueClient
4
+
5
+ GUEST = 'guest'.freeze
6
+
7
+ def initialize(options = {})
8
+
9
+ if options[:endpoint] == nil
10
+ raise ':endpoint must be specified.'.freeze
11
+ end
12
+
13
+ @endpoint = options[:endpoint]
14
+
15
+ @port = Integer(options[:port] || 5672)
16
+
17
+ @user = options[:user] || GUEST
18
+
19
+ @password = options[:password] || GUEST
20
+
21
+ @ssl = options[:ssl] == true || false
22
+
23
+ end
24
+
25
+ def connection_options
26
+ {
27
+ :host => @endpoint,
28
+ :port => @port,
29
+ :user => @user,
30
+ :pass => @password,
31
+ :ssl => @ssl,
32
+ :read_timeout => 4,
33
+ :heartbeat => 8,
34
+ :continuation_timeout => 5000,
35
+ :automatically_recover => true,
36
+ :network_recovery_interval => 1,
37
+ :recover_from_connection_close => true
38
+ }
39
+ end
40
+
41
+ def get_connection
42
+ if RUBY_PLATFORM =~ /java/
43
+ conn = MarchHare.connect(connection_options)
44
+ else
45
+ conn = Bunny.new(connection_options)
46
+ end
47
+
48
+ conn.start
49
+ return conn
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,104 @@
1
+ module EventQ
2
+ module RabbitMq
3
+ class QueueManager
4
+
5
+ X_DEAD_LETTER_EXCHANGE = 'x-dead-letter-exchange'.freeze
6
+ X_MESSAGE_TTL = 'x-message-ttl'.freeze
7
+
8
+ attr_accessor :durable
9
+
10
+ def initialize
11
+ @event_raised_exchange = EventQ::EventRaisedExchange.new
12
+ @durable = true
13
+ end
14
+
15
+ def get_queue(channel, queue)
16
+
17
+ _queue_name = EventQ.create_queue_name(queue.name)
18
+
19
+ #get/create the queue
20
+ q = channel.queue(_queue_name, :durable => @durable)
21
+
22
+ if queue.allow_retry
23
+ retry_exchange = get_retry_exchange(channel, queue)
24
+ subscriber_exchange = get_subscriber_exchange(channel, queue)
25
+
26
+ retry_queue = get_retry_queue(channel, queue)
27
+ retry_queue.bind(retry_exchange)
28
+
29
+ q.bind(subscriber_exchange)
30
+ end
31
+
32
+ return q
33
+ end
34
+
35
+ def pop_message(queue:)
36
+ if RUBY_PLATFORM =~ /java/
37
+ headers, payload = queue.pop({ :ack => true, :block => true })
38
+ if headers == nil
39
+ return [nil,nil]
40
+ end
41
+ [headers.delivery_tag, payload]
42
+ else
43
+ headers, properties, payload = queue.pop({ :manual_ack => true, :block => true })
44
+ if headers == nil
45
+ return [nil,nil]
46
+ end
47
+ [headers.delivery_tag, payload]
48
+ end
49
+ end
50
+
51
+ def get_queue_exchange(channel, queue)
52
+ _exchange_name = EventQ.create_exchange_name(queue.name)
53
+ channel.direct("#{_exchange_name}.ex")
54
+ end
55
+
56
+ def get_retry_exchange(channel, queue)
57
+ _queue_name = EventQ.create_queue_name(queue.name)
58
+ return channel.fanout("#{_queue_name}.r.ex")
59
+ end
60
+
61
+ def get_subscriber_exchange(channel, queue)
62
+ _queue_name = EventQ.create_queue_name(queue.name)
63
+ return channel.fanout("#{_queue_name}.ex")
64
+ end
65
+
66
+ def get_delay_exchange(channel, queue, delay)
67
+ _queue_name = EventQ.create_queue_name(queue.name)
68
+ channel.direct("#{_queue_name}.#{delay}.d.ex")
69
+ end
70
+
71
+ def get_retry_queue(channel, queue)
72
+ subscriber_exchange = get_subscriber_exchange(channel, queue)
73
+
74
+ _queue_name = EventQ.create_queue_name(queue.name)
75
+
76
+ if queue.allow_retry_back_off == true
77
+
78
+ EventQ.logger.debug { "[#{self.class}] - Requesting retry queue. x-dead-letter-exchange: #{subscriber_exchange.name} | x-message-ttl: #{queue.max_retry_delay}" }
79
+
80
+ return channel.queue("#{_queue_name}.r", :durable => @durable, :arguments => { X_DEAD_LETTER_EXCHANGE => subscriber_exchange.name, X_MESSAGE_TTL => queue.max_retry_delay })
81
+
82
+ else
83
+
84
+ EventQ.logger.debug { "[#{self.class}] - Requesting retry queue. x-dead-letter-exchange: #{subscriber_exchange.name} | x-message-ttl: #{queue.retry_delay}" }
85
+
86
+ return channel.queue("#{_queue_name}.r", :durable => @durable, :arguments => { X_DEAD_LETTER_EXCHANGE => subscriber_exchange.name, X_MESSAGE_TTL => queue.retry_delay })
87
+
88
+ end
89
+
90
+ end
91
+
92
+ def create_delay_queue(channel, queue, dlx_name, delay=0)
93
+ queue_name = EventQ.create_queue_name(queue.name)
94
+ channel.queue("#{queue_name}.#{delay}.delay", durable: @durable,
95
+ arguments: { X_DEAD_LETTER_EXCHANGE => dlx_name, X_MESSAGE_TTL => delay * 1000 })
96
+ end
97
+
98
+ def get_exchange(channel, exchange)
99
+ _exchange_name = EventQ.create_exchange_name(exchange.name)
100
+ return channel.direct(_exchange_name, :durable => @durable)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,389 @@
1
+ module EventQ
2
+ module RabbitMq
3
+ class QueueWorker
4
+ include EventQ::WorkerId
5
+
6
+ attr_accessor :is_running
7
+
8
+ def initialize
9
+ @threads = []
10
+ @forks = []
11
+ @is_running = false
12
+
13
+ @retry_exceeded_block = nil
14
+ @on_retry_block = nil
15
+ @on_error_block = nil
16
+ @hash_helper = HashKit::Helper.new
17
+ @serialization_provider_manager = EventQ::SerializationProviders::Manager.new
18
+ @signature_provider_manager = EventQ::SignatureProviders::Manager.new
19
+ @last_gc_flush = Time.now
20
+ @gc_flush_interval = 10
21
+ end
22
+
23
+ def start(queue, options = {}, &block)
24
+
25
+ EventQ.logger.info("[#{self.class}] - Preparing to start listening for messages.")
26
+
27
+ configure(queue, options)
28
+
29
+ raise "[#{self.class}] - Worker is already running." if running?
30
+
31
+ if options[:client] == nil
32
+ raise "[#{self.class}] - :client (QueueClient) must be specified."
33
+ end
34
+
35
+ EventQ.logger.info("[#{self.class}] - Listening for messages.")
36
+ EventQ.logger.debug do
37
+ "[#{self.class} #start] - Listening for messages on queue: #{EventQ.create_queue_name(queue.name)}"
38
+ end
39
+
40
+ @forks = []
41
+
42
+ if @fork_count > 1
43
+ @fork_count.times do
44
+ pid = fork do
45
+ start_process(options, queue, block)
46
+ end
47
+ @forks.push(pid)
48
+ end
49
+
50
+ if options.key?(:wait) && options[:wait] == true
51
+ @forks.each { |pid| Process.wait(pid) }
52
+ end
53
+
54
+ else
55
+ start_process(options, queue, block)
56
+ end
57
+
58
+ end
59
+
60
+ def start_process(options, queue, block)
61
+
62
+ @is_running = true
63
+
64
+ %w'INT TERM'.each do |sig|
65
+ Signal.trap(sig) {
66
+ stop
67
+ exit
68
+ }
69
+ end
70
+
71
+ if !options.key?(:durable)
72
+ options[:durable] = true
73
+ end
74
+
75
+ client = options[:client]
76
+ manager = EventQ::RabbitMq::QueueManager.new
77
+ manager.durable = options[:durable]
78
+ @connection = client.get_connection
79
+
80
+ @threads = []
81
+
82
+ #loop through each thread count
83
+ @thread_count.times do
84
+ thr = Thread.new do
85
+
86
+ #begin the queue loop for this thread
87
+ while true do
88
+
89
+ #check if the worker is still allowed to run and break out of thread loop if not
90
+ unless running?
91
+ break
92
+ end
93
+
94
+ has_received_message = false
95
+
96
+ begin
97
+
98
+ channel = @connection.create_channel
99
+
100
+ has_received_message = thread_process_iteration(channel, manager, queue, block)
101
+
102
+ rescue => e
103
+ EventQ.logger.error("An unhandled error occurred. Error: #{e} | Backtrace: #{e.backtrace}")
104
+ call_on_error_block(error: e)
105
+ end
106
+
107
+ if channel != nil && channel.open?
108
+ channel.close
109
+ end
110
+
111
+ gc_flush
112
+
113
+ if !has_received_message
114
+ EventQ.logger.debug { "[#{self.class}] - No message received." }
115
+ if @sleep > 0
116
+ EventQ.logger.debug { "[#{self.class}] - Sleeping for #{@sleep} seconds" }
117
+ sleep(@sleep)
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+ @threads.push(thr)
125
+
126
+ end
127
+
128
+ if options.key?(:wait) && options[:wait] == true
129
+ @threads.each { |thr| thr.join }
130
+ @connection.close if @connection.open?
131
+ end
132
+
133
+ return true
134
+
135
+ end
136
+
137
+ def call_on_error_block(error:, message: nil)
138
+ if @on_error_block
139
+ EventQ.logger.debug { "[#{self.class}] - Executing on_error block." }
140
+ begin
141
+ @on_error_block.call(error, message)
142
+ rescue => e
143
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_error block. Error: #{e}")
144
+ end
145
+ else
146
+ EventQ.logger.debug { "[#{self.class}] - No on_error block specified to execute." }
147
+ end
148
+ end
149
+
150
+ def gc_flush
151
+ if Time.now - last_gc_flush > @gc_flush_interval
152
+ GC.start
153
+ @last_gc_flush = Time.now
154
+ end
155
+ end
156
+
157
+ def last_gc_flush
158
+ @last_gc_flush
159
+ end
160
+
161
+ def thread_process_iteration(channel, manager, queue, block)
162
+
163
+ #get the queue
164
+ q = manager.get_queue(channel, queue)
165
+ retry_exchange = manager.get_retry_exchange(channel, queue)
166
+
167
+ received = false
168
+
169
+ begin
170
+ delivery_info, payload = manager.pop_message(queue: q)
171
+
172
+ #check that message was received
173
+ if payload != nil
174
+ received = true
175
+ begin
176
+ tag_processing_thread
177
+ process_message(payload, queue, channel, retry_exchange, delivery_info, block)
178
+ ensure
179
+ untag_processing_thread
180
+ end
181
+
182
+ end
183
+
184
+ rescue => e
185
+ EventQ.logger.error("[#{self.class}] - An error occurred attempting to process a message. Error: #{e} | Backtrace: #{e.backtrace}")
186
+ call_on_error_block(error: e)
187
+ end
188
+
189
+ return received
190
+ end
191
+
192
+ def stop
193
+ EventQ.logger.info { "[#{self.class}] - Stopping..." }
194
+ @is_running = false
195
+ Thread.list.each do |thread|
196
+ thread.exit unless thread == Thread.current
197
+ end
198
+ if @connection != nil
199
+ begin
200
+ @connection.close if @connection.open?
201
+ rescue Timeout::Error
202
+ EventQ.logger.error { 'Timeout occurred closing connection.' }
203
+ end
204
+ end
205
+ return true
206
+ end
207
+
208
+ def on_retry_exceeded(&block)
209
+ @retry_exceeded_block = block
210
+ return nil
211
+ end
212
+
213
+ def on_retry(&block)
214
+ @on_retry_block = block
215
+ return nil
216
+ end
217
+
218
+ def on_error(&block)
219
+ @on_error_block = block
220
+ return nil
221
+ end
222
+
223
+ def running?
224
+ return @is_running
225
+ end
226
+
227
+ def deserialize_message(payload)
228
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
229
+ return provider.deserialize(payload)
230
+ end
231
+
232
+ def serialize_message(msg)
233
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
234
+ return provider.serialize(msg)
235
+ end
236
+
237
+ def call_on_retry_exceeded_block(message)
238
+ if @retry_exceeded_block != nil
239
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry_exceeded block." }
240
+ begin
241
+ @retry_exceeded_block.call(message)
242
+ rescue => e
243
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry_exceeded block. Error: #{e}")
244
+ end
245
+ else
246
+ EventQ.logger.debug { "[#{self.class}] - No on_retry_exceeded block specified." }
247
+ end
248
+ end
249
+
250
+ def call_on_retry_block(message)
251
+ if @on_retry_block
252
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry block." }
253
+ begin
254
+ @on_retry_block.call(message, abort)
255
+ rescue => e
256
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry block. Error: #{e}")
257
+ end
258
+ else
259
+ EventQ.logger.debug { "[#{self.class}] - No on_retry block specified." }
260
+ end
261
+ end
262
+
263
+ def reject_message(channel, message, delivery_tag, retry_exchange, queue, abort)
264
+
265
+ EventQ.logger.info("[#{self.class}] - Message rejected removing from queue.")
266
+ #reject the message to remove from queue
267
+ channel.reject(delivery_tag, false)
268
+
269
+ #check if the message retry limit has been exceeded
270
+ if message.retry_attempts >= queue.max_retry_attempts
271
+
272
+ EventQ.logger.info("[#{self.class}] - Message retry attempt limit exceeded. Msg: #{serialize_message(message)}")
273
+
274
+ call_on_retry_exceeded_block(message)
275
+
276
+ #check if the message is allowed to be retried
277
+ elsif queue.allow_retry
278
+
279
+ EventQ.logger.debug { "[#{self.class}] - Incrementing retry attempts count." }
280
+ message.retry_attempts += 1
281
+
282
+ if queue.allow_retry_back_off == true
283
+ EventQ.logger.debug { "[#{self.class}] - Calculating message back off retry delay. Attempts: #{message.retry_attempts} * Retry Delay: #{queue.retry_delay}" }
284
+ message_ttl = message.retry_attempts * queue.retry_delay
285
+ if (message.retry_attempts * queue.retry_delay) > queue.max_retry_delay
286
+ EventQ.logger.debug { "[#{self.class}] - Max message back off retry delay reached." }
287
+ message_ttl = queue.max_retry_delay
288
+ end
289
+ else
290
+ EventQ.logger.debug { "[#{self.class}] - Setting fixed retry delay for message." }
291
+ message_ttl = queue.retry_delay
292
+ end
293
+
294
+ EventQ.logger.debug { "[#{self.class}] - Sending message for retry. Message TTL: #{message_ttl}" }
295
+ retry_exchange.publish(serialize_message(message), :expiration => message_ttl)
296
+ EventQ.logger.debug { "[#{self.class}] - Published message to retry exchange." }
297
+
298
+ call_on_retry_block(message)
299
+
300
+ end
301
+
302
+ return true
303
+
304
+ end
305
+
306
+ def configure(queue, options = {})
307
+
308
+ @queue = queue
309
+
310
+ #default thread count
311
+ @thread_count = 4
312
+ if options.key?(:thread_count)
313
+ @thread_count = options[:thread_count]
314
+ end
315
+
316
+ #default sleep time in seconds
317
+ @sleep = 15
318
+ if options.key?(:sleep)
319
+ @sleep = options[:sleep]
320
+ end
321
+
322
+ @fork_count = 1
323
+ if options.key?(:fork_count)
324
+ @fork_count = options[:fork_count]
325
+ end
326
+
327
+ @gc_flush_interval = 10
328
+ if options.key?(:gc_flush_interval)
329
+ @gc_flush_interval = options[:gc_flush_interval]
330
+ end
331
+
332
+ EventQ.logger.info("[#{self.class}] - Configuring. Process Count: #{@fork_count} | Thread Count: #{@thread_count} | Interval Sleep: #{@sleep}.")
333
+
334
+ return true
335
+
336
+ end
337
+
338
+ private
339
+
340
+ def process_message(payload, queue, channel, retry_exchange, delivery_tag, block)
341
+ abort = false
342
+ error = false
343
+ message = deserialize_message(payload)
344
+
345
+ EventQ.logger.info("[#{self.class}] - Message received. Retry Attempts: #{message.retry_attempts}")
346
+
347
+ @signature_provider_manager.validate_signature(message: message, queue: queue)
348
+
349
+ message_args = EventQ::MessageArgs.new(type: message.type,
350
+ retry_attempts: message.retry_attempts,
351
+ context: message.context,
352
+ content_type: message.content_type)
353
+
354
+ if(!EventQ::NonceManager.is_allowed?(message.id))
355
+ EventQ.logger.info("[#{self.class}] - Duplicate Message received. Dropping message.")
356
+ channel.acknowledge(delivery_tag, false)
357
+ return false
358
+ end
359
+
360
+ #begin worker block for queue message
361
+ begin
362
+ block.call(message.content, message_args)
363
+
364
+ if message_args.abort == true
365
+ abort = true
366
+ EventQ.logger.info("[#{self.class}] - Message aborted.")
367
+ else
368
+ #accept the message as processed
369
+ channel.acknowledge(delivery_tag, false)
370
+ EventQ.logger.info("[#{self.class}] - Message acknowledged.")
371
+ end
372
+
373
+ rescue => e
374
+ EventQ.logger.error("[#{self.class}] - An unhandled error happened attempting to process a queue message. Error: #{e} | Backtrace: #{e.backtrace}")
375
+ error = true
376
+ call_on_error_block(error: e, message: message)
377
+ end
378
+
379
+ if error || abort
380
+ EventQ::NonceManager.failed(message.id)
381
+ reject_message(channel, message, delivery_tag, retry_exchange, queue, abort)
382
+ else
383
+ EventQ::NonceManager.complete(message.id)
384
+ end
385
+ end
386
+ end
387
+ end
388
+ end
389
+
@@ -0,0 +1,62 @@
1
+ module EventQ
2
+ module RabbitMq
3
+ class StatusChecker
4
+
5
+ def initialize(client:, queue_manager:)
6
+
7
+ if client == nil
8
+ raise 'client must be specified.'.freeze
9
+ end
10
+
11
+ @client = client
12
+
13
+ if queue_manager == nil
14
+ raise 'queue_manager must be specified.'.freeze
15
+ end
16
+
17
+ @queue_manager = queue_manager
18
+
19
+ @event_raised_exchange = EventRaisedExchange.new
20
+
21
+ end
22
+
23
+ def queue?(queue)
24
+
25
+ outcome = true
26
+
27
+ begin
28
+ connection = @client.get_connection
29
+ channel = connection.create_channel
30
+ _queue_name = EventQ.create_queue_name(queue.name)
31
+ channel.queue(_queue_name, :durable => true)
32
+ rescue
33
+ outcome = false
34
+ ensure
35
+ channel.close if channel
36
+ connection.close if connection
37
+ end
38
+
39
+ outcome
40
+ end
41
+
42
+ def event_type?(event_type)
43
+
44
+ outcome = true
45
+
46
+ begin
47
+ connection = @client.get_connection
48
+ channel = connection.create_channel
49
+ @queue_manager.get_exchange(channel, @event_raised_exchange)
50
+ rescue
51
+ outcome = false
52
+ ensure
53
+ channel.close if channel
54
+ connection.close if connection
55
+ end
56
+
57
+ outcome
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ module EventQ
2
+ module RabbitMq
3
+ class SubscriptionManager
4
+
5
+ def initialize(options = {})
6
+ if options[:client] == nil
7
+ raise ':client (QueueClient) must be specified.'.freeze
8
+ end
9
+ @client = options[:client]
10
+ @queue_manager = QueueManager.new
11
+ @event_raised_exchange = EventQ::EventRaisedExchange.new
12
+ end
13
+
14
+ def subscribe(event_type, queue)
15
+
16
+ _event_type = EventQ.create_event_type(event_type)
17
+
18
+ connection = @client.get_connection
19
+ channel = connection.create_channel
20
+
21
+ queue = @queue_manager.get_queue(channel, queue)
22
+ exchange = @queue_manager.get_exchange(channel, @event_raised_exchange)
23
+
24
+ queue.bind(exchange, :routing_key => _event_type)
25
+
26
+ channel.close
27
+ connection.close
28
+
29
+ EventQ.logger.debug do
30
+ "[#{self.class} #subscribe] - Subscribing queue: #{EventQ.create_queue_name(queue.name)} to Exchange: #{_event_type}"
31
+ end
32
+
33
+ return true
34
+ end
35
+
36
+ def unsubscribe(queue)
37
+
38
+ connection = @client.get_connection
39
+ channel = connection.create_channel
40
+
41
+ queue = @queue_manager.get_queue(channel, queue)
42
+ exchange = @queue_manager.get_exchange(channel, @event_raised_exchange)
43
+
44
+ queue.unbind(exchange)
45
+
46
+ channel.close
47
+ connection.close
48
+
49
+ return true
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ module EventqRabbitmq
2
+ VERSION = "1.17.0"
3
+ end
@@ -0,0 +1,49 @@
1
+ require 'eventq_base'
2
+ if RUBY_PLATFORM =~ /java/
3
+ require 'march_hare'
4
+ else
5
+ require 'bunny'
6
+ end
7
+
8
+ require 'hash_kit'
9
+ require_relative '../lib/eventq_rabbitmq/version'
10
+ require_relative '../lib/eventq_rabbitmq/rabbitmq_queue_client'
11
+ require_relative '../lib/eventq_rabbitmq/rabbitmq_queue_manager'
12
+
13
+ if RUBY_PLATFORM =~ /java/
14
+ require_relative '../lib/eventq_rabbitmq/jruby/rabbitmq_queue_worker'
15
+ else
16
+ require_relative '../lib/eventq_rabbitmq/rabbitmq_queue_worker'
17
+ end
18
+
19
+ require_relative '../lib/eventq_rabbitmq/rabbitmq_subscription_manager'
20
+ require_relative '../lib/eventq_rabbitmq/rabbitmq_eventq_client'
21
+ require_relative '../lib/eventq_rabbitmq/default_queue'
22
+ require_relative '../lib/eventq_rabbitmq/rabbitmq_status_checker'
23
+
24
+ module EventQ
25
+ def self.namespace
26
+ @namespace
27
+ end
28
+ def self.namespace=(value)
29
+ @namespace = value
30
+ end
31
+ def self.create_event_type(event_type)
32
+ if EventQ.namespace == nil
33
+ return event_type
34
+ end
35
+ return "#{EventQ.namespace}-#{event_type}"
36
+ end
37
+ def self.create_queue_name(queue_name)
38
+ if EventQ.namespace == nil
39
+ return queue_name
40
+ end
41
+ return "#{EventQ.namespace}-#{queue_name}"
42
+ end
43
+ def self.create_exchange_name(exchange_name)
44
+ if EventQ.namespace == nil
45
+ return exchange_name
46
+ end
47
+ return "#{EventQ.namespace}-#{exchange_name}"
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eventq_rabbitmq
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.17.0
5
+ platform: java
6
+ authors:
7
+ - vaughanbrittonsage
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.11'
19
+ name: bundler
20
+ prerelease: false
21
+ type: :development
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '10.0'
33
+ name: rake
34
+ prerelease: false
35
+ type: :development
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ name: rspec
48
+ prerelease: false
49
+ type: :development
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.15'
61
+ name: eventq_base
62
+ prerelease: false
63
+ type: :runtime
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.15'
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ name: march_hare
76
+ prerelease: false
77
+ type: :runtime
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: This is the rabbitmq implementation for EventQ
84
+ email:
85
+ - vaughanbritton@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - bin/console
91
+ - bin/setup
92
+ - lib/eventq_rabbitmq.rb
93
+ - lib/eventq_rabbitmq/default_queue.rb
94
+ - lib/eventq_rabbitmq/jruby/rabbitmq_queue_worker.rb
95
+ - lib/eventq_rabbitmq/rabbitmq_eventq_client.rb
96
+ - lib/eventq_rabbitmq/rabbitmq_queue_client.rb
97
+ - lib/eventq_rabbitmq/rabbitmq_queue_manager.rb
98
+ - lib/eventq_rabbitmq/rabbitmq_queue_worker.rb
99
+ - lib/eventq_rabbitmq/rabbitmq_status_checker.rb
100
+ - lib/eventq_rabbitmq/rabbitmq_subscription_manager.rb
101
+ - lib/eventq_rabbitmq/version.rb
102
+ homepage: https://github.com/vaughanbrittonsage/eventq
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.6.11
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: This is the rabbitmq implementation for EventQ
126
+ test_files: []