eventq_rabbitmq 1.17.0-java

Sign up to get free protection for your applications and to get access to all the features.
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: []