eventq_aws 1.14.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: 184374221348bf33be9f74cd8d4d8b0f26b9dea45056bd13f1eb930d03149a81
4
+ data.tar.gz: 3c5a842fed0e4e52ebf001b91a76348d5fb4f5c6db53ec516aaecaf523ed1b29
5
+ SHA512:
6
+ metadata.gz: daea0b813cac8d208a55c6882bdcbf17f4e602779f6da0981be747d7ec8b88ca63734b7d625b2e5739b94b89f72f48fc5a2535c83f7d519805161016e8c5e840
7
+ data.tar.gz: 2a9121ab2e4fe069fe0a4d452cfd5e5d675049a41367787836cf03f5b63964b6c09218cd9f4ff0352035c1f62a04b0cba3b75ec1e12719fd033aa73b06d32829
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "eventq_aws"
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,116 @@
1
+ module EventQ
2
+ module Amazon
3
+ # Implements a general interface to raise an event
4
+ # EventQ::RabbitMq::EventQClient is the sister-class which does the same for RabbitMq
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
+
15
+ @serialization_manager = EventQ::SerializationProviders::Manager.new
16
+ @signature_manager = EventQ::SignatureProviders::Manager.new
17
+
18
+ #this array is used to record known event types
19
+ @known_event_types = []
20
+
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
+ @client.create_topic_arn(event_type)
33
+ @known_event_types << event_type
34
+ true
35
+ end
36
+
37
+ def publish(topic:, event:, context: {})
38
+ raise_event(topic, event, context)
39
+ end
40
+
41
+ def raise_event(event_type, event, context = {})
42
+ register_event(event_type)
43
+
44
+ with_prepared_message(event_type, event, context) do |message|
45
+
46
+ response = @client.sns.publish(
47
+ topic_arn: topic_arn(event_type),
48
+ message: message,
49
+ subject: event_type
50
+ )
51
+
52
+ EventQ.logger.debug do
53
+ "[#{self.class} #raise_event] - Published to SNS with topic_arn: #{topic_arn(event_type)} | event_type: #{event_type} | Message: #{message}"
54
+ end
55
+
56
+ response
57
+ end
58
+ end
59
+
60
+ def raise_event_in_queue(event_type, event, queue, delay, context = {})
61
+ queue_url = @client.get_queue_url(queue)
62
+ with_prepared_message(event_type, event, context) do |message|
63
+
64
+ response = @client.sqs.send_message(
65
+ queue_url: queue_url,
66
+ message_body: message,
67
+ delay_seconds: delay
68
+ )
69
+
70
+ EventQ.logger.debug do
71
+ "[#{self.class} #raise_event_in_queue] - Raised event to SQS queue: #{queue_url} | event_type: #{event_type} | Message: #{message}"
72
+ end
73
+
74
+ response
75
+ end
76
+ end
77
+
78
+ def new_message
79
+ EventQ::QueueMessage.new
80
+ end
81
+
82
+ private
83
+
84
+ def with_prepared_message(event_type, event, context)
85
+ qm = new_message
86
+ qm.content = event
87
+ qm.type = event_type
88
+ qm.context = context
89
+ qm.content_type = event.class.to_s
90
+
91
+ if EventQ::Configuration.signature_secret != nil
92
+ provider = @signature_manager.get_provider(EventQ::Configuration.signature_provider)
93
+ qm.signature = provider.write(message: qm, secret: EventQ::Configuration.signature_secret)
94
+ end
95
+
96
+ message = serialized_message(qm)
97
+
98
+ response = yield(message)
99
+
100
+ EventQ.log(:debug, "[#{self.class}] - Raised event. Message: #{message} | Type: #{event_type}.")
101
+
102
+ response.message_id
103
+ end
104
+
105
+ def serialized_message(queue_message)
106
+ serialization_provider = @serialization_manager.get_provider(EventQ::Configuration.serialization_provider)
107
+
108
+ serialization_provider.serialize(queue_message)
109
+ end
110
+
111
+ def topic_arn(event_type)
112
+ @client.get_topic_arn(event_type)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,68 @@
1
+ module EventQ
2
+ module Amazon
3
+ class QueueClient
4
+
5
+ def initialize(options = {})
6
+ if options.has_key?(:aws_key)
7
+ Aws.config[:credentials] = Aws::Credentials.new(options[:aws_key], options[:aws_secret])
8
+ end
9
+
10
+ if !options.has_key?(:aws_account_number)
11
+ raise ':aws_account_number option must be specified.'.freeze
12
+ end
13
+
14
+ @aws_account = options[:aws_account_number]
15
+
16
+ if options.has_key?(:aws_region)
17
+ @aws_region = options[:aws_region]
18
+ Aws.config[:region] = @aws_region
19
+ else
20
+ @aws_region = Aws.config[:region]
21
+ end
22
+ end
23
+
24
+ # Returns the AWS SQS Client
25
+ def sqs
26
+ @sqs ||= Aws::SQS::Client.new
27
+ end
28
+
29
+ # Returns the AWS SNS Client
30
+ def sns
31
+ @sns ||= Aws::SNS::Client.new
32
+ end
33
+
34
+ def get_topic_arn(event_type)
35
+ _event_type = EventQ.create_event_type(event_type)
36
+ return "arn:aws:sns:#{@aws_region}:#{@aws_account}:#{aws_safe_name(_event_type)}"
37
+ end
38
+
39
+ def get_queue_arn(queue)
40
+ _queue_name = EventQ.create_queue_name(queue.name)
41
+ return "arn:aws:sqs:#{@aws_region}:#{@aws_account}:#{aws_safe_name(_queue_name)}"
42
+ end
43
+
44
+ def create_topic_arn(event_type)
45
+ _event_type = EventQ.create_event_type(event_type)
46
+ response = sns.create_topic(name: aws_safe_name(_event_type))
47
+ return response.topic_arn
48
+ end
49
+
50
+ # Returns the URL of the queue. The queue will be created when it does
51
+ #
52
+ # @param queue [EventQ::Queue]
53
+ def get_queue_url(queue)
54
+ _queue_name = EventQ.create_queue_name(queue.name)
55
+ response= sqs.get_queue_url(
56
+ queue_name: aws_safe_name(_queue_name),
57
+ queue_owner_aws_account_id: @aws_account,
58
+ )
59
+ return response.queue_url
60
+ end
61
+
62
+ def aws_safe_name(name)
63
+ return name[0..79].gsub(/[^a-zA-Z\d_\-]/,'')
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,98 @@
1
+ module EventQ
2
+ module Amazon
3
+ class QueueManager
4
+
5
+ VISIBILITY_TIMEOUT = 'VisibilityTimeout'.freeze
6
+ MESSAGE_RETENTION_PERIOD = 'MessageRetentionPeriod'.freeze
7
+
8
+ def initialize(options)
9
+
10
+ if options[:client] == nil
11
+ raise ':client (QueueClient) must be specified.'.freeze
12
+ end
13
+
14
+ @client = options[:client]
15
+
16
+ @visibility_timeout = 300 #5 minutes
17
+ if options.key?(:visibility_timeout)
18
+ @visibility_timeout = options[:visibility_timeout]
19
+ end
20
+
21
+ @message_retention_period = 1209600 #14 days (max aws value)
22
+ if options.key?(:message_retention_period)
23
+ @message_retention_period = options[:message_retention_period]
24
+ end
25
+
26
+ end
27
+
28
+ def get_queue(queue)
29
+
30
+ if queue_exists?(queue)
31
+ update_queue(queue)
32
+ else
33
+ create_queue(queue)
34
+ end
35
+
36
+ end
37
+
38
+ def create_queue(queue)
39
+ _queue_name = EventQ.create_queue_name(queue.name)
40
+ response = @client.sqs.create_queue({
41
+ queue_name: _queue_name,
42
+ attributes: {
43
+ VISIBILITY_TIMEOUT => @visibility_timeout.to_s,
44
+ MESSAGE_RETENTION_PERIOD => @message_retention_period.to_s
45
+ }
46
+ })
47
+
48
+ return response.queue_url
49
+ end
50
+
51
+ def drop_queue(queue)
52
+
53
+ q = get_queue(queue)
54
+
55
+ @client.sqs.delete_queue({ queue_url: q})
56
+
57
+ return true
58
+
59
+ end
60
+
61
+ def drop_topic(event_type)
62
+ topic_arn = @client.get_topic_arn(event_type)
63
+ @client.sns.delete_topic({ topic_arn: topic_arn})
64
+
65
+ return true
66
+ end
67
+
68
+ def topic_exists?(event_type)
69
+ topic_arn = @client.get_topic_arn(event_type)
70
+
71
+ begin
72
+ @client.sns.get_topic_attributes({ topic_arn: topic_arn })
73
+ rescue
74
+ return false
75
+ end
76
+ return true
77
+ end
78
+
79
+ def queue_exists?(queue)
80
+ _queue_name = EventQ.create_queue_name(queue.name)
81
+ return @client.sqs.list_queues({ queue_name_prefix: _queue_name }).queue_urls.length > 0
82
+ end
83
+
84
+ def update_queue(queue)
85
+ url = @client.get_queue_url(queue)
86
+ @client.sqs.set_queue_attributes({
87
+ queue_url: url, # required
88
+ attributes: {
89
+ VISIBILITY_TIMEOUT => @visibility_timeout.to_s,
90
+ MESSAGE_RETENTION_PERIOD => @message_retention_period.to_s
91
+ }
92
+ })
93
+ return url
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,386 @@
1
+ module EventQ
2
+ module Amazon
3
+ class QueueWorker
4
+ include EventQ::WorkerId
5
+
6
+ APPROXIMATE_RECEIVE_COUNT = 'ApproximateReceiveCount'.freeze
7
+ MESSAGE = 'Message'.freeze
8
+
9
+ attr_accessor :is_running
10
+
11
+ def initialize
12
+ @threads = []
13
+ @forks = []
14
+ @is_running = false
15
+
16
+ @on_retry_exceeded_block = nil
17
+ @on_retry_block = nil
18
+ @on_error_block = nil
19
+
20
+ @hash_helper = HashKit::Helper.new
21
+ @serialization_provider_manager = EventQ::SerializationProviders::Manager.new
22
+ @signature_provider_manager = EventQ::SignatureProviders::Manager.new
23
+
24
+ @last_gc_flush = Time.now
25
+ @gc_flush_interval = 10
26
+
27
+ @queue_poll_wait = 10
28
+ end
29
+
30
+ def start(queue, options = {}, &block)
31
+
32
+ EventQ.logger.info("[#{self.class}] - Preparing to start listening for messages.")
33
+
34
+ configure(queue, options)
35
+
36
+ if options[:client] == nil
37
+ raise "[#{self.class}] - :client (QueueClient) must be specified."
38
+ end
39
+
40
+ raise "[#{self.class}] - Worker is already running." if running?
41
+
42
+ client = options[:client]
43
+ EventQ.logger.debug do
44
+ "[#{self.class} #start] - Listening for messages on queue: #{queue.name}, Queue Url: #{client.get_queue_url(queue)}, Queue arn: #{client.get_queue_arn(queue)}"
45
+ end
46
+
47
+ EventQ.logger.info("[#{self.class}] - Listening for messages.")
48
+
49
+ @forks = []
50
+
51
+ if @fork_count > 1
52
+ @fork_count.times do
53
+ pid = fork do
54
+ start_process(options, queue, block)
55
+ end
56
+ @forks.push(pid)
57
+ end
58
+
59
+ if options.key?(:wait) && options[:wait] == true
60
+ @forks.each { |pid| Process.wait(pid) }
61
+ end
62
+
63
+ else
64
+ start_process(options, queue, block)
65
+ end
66
+
67
+ return true
68
+ end
69
+
70
+ def start_process(options, queue, block)
71
+
72
+ %w'INT TERM'.each do |sig|
73
+ Signal.trap(sig) {
74
+ stop
75
+ exit
76
+ }
77
+ end
78
+
79
+ @is_running = true
80
+ @threads = []
81
+
82
+ #loop through each thread count
83
+ @thread_count.times do
84
+ thr = Thread.new do
85
+
86
+ client = options[:client]
87
+ manager = EventQ::Amazon::QueueManager.new({ client: client })
88
+
89
+ #begin the queue loop for this thread
90
+ while true do
91
+
92
+ #check if the worker is still allowed to run and break out of thread loop if not
93
+ if !@is_running
94
+ break
95
+ end
96
+
97
+ has_message_received = thread_process_iteration(client, manager, queue, block)
98
+
99
+ gc_flush
100
+
101
+ if !has_message_received
102
+ EventQ.logger.debug { "[#{self.class}] - No message received." }
103
+ if @sleep > 0
104
+ EventQ.logger.debug { "[#{self.class}] - Sleeping for #{@sleep} seconds" }
105
+ sleep(@sleep)
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+ @threads.push(thr)
113
+
114
+ end
115
+
116
+ if options.key?(:wait) && options[:wait] == true
117
+ @threads.each { |thr| thr.join }
118
+ end
119
+
120
+ end
121
+
122
+ def gc_flush
123
+ if Time.now - last_gc_flush > @gc_flush_interval
124
+ GC.start
125
+ @last_gc_flush = Time.now
126
+ end
127
+ end
128
+
129
+ def last_gc_flush
130
+ @last_gc_flush
131
+ end
132
+
133
+ def thread_process_iteration(client, manager, queue, block)
134
+ #get the queue
135
+ q = manager.get_queue(queue)
136
+
137
+ received = false
138
+
139
+ begin
140
+
141
+ #request a message from the queue
142
+ response = client.sqs.receive_message({
143
+ queue_url: q,
144
+ max_number_of_messages: 1,
145
+ wait_time_seconds: @queue_poll_wait,
146
+ attribute_names: [APPROXIMATE_RECEIVE_COUNT]
147
+ })
148
+
149
+ #check that a message was received
150
+ if response.messages.length > 0
151
+ received = true
152
+ begin
153
+ tag_processing_thread
154
+ process_message(response, client, queue, q, block)
155
+ ensure
156
+ untag_processing_thread
157
+ end
158
+
159
+ end
160
+
161
+ rescue => e
162
+ EventQ.log(:error, "[#{self.class}] - An unhandled error occurred. Error: #{e} | Backtrace: #{e.backtrace}")
163
+ call_on_error_block(error: e)
164
+ end
165
+
166
+ return received
167
+ end
168
+
169
+ def call_on_error_block(error:, message: nil)
170
+ if @on_error_block
171
+ EventQ.logger.debug { "[#{self.class}] - Executing on_error block." }
172
+ begin
173
+ @on_error_block.call(error, message)
174
+ rescue => e
175
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_error block. Error: #{e}")
176
+ end
177
+ else
178
+ EventQ.logger.debug { "[#{self.class}] - No on_error block specified to execute." }
179
+ end
180
+ end
181
+
182
+ def call_on_retry_exceeded_block(message)
183
+ if @on_retry_exceeded_block != nil
184
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry_exceeded block." }
185
+ begin
186
+ @on_retry_exceeded_block.call(message)
187
+ rescue => e
188
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry_exceeded block. Error: #{e}")
189
+ end
190
+ else
191
+ EventQ.logger.debug { "[#{self.class}] - No on_retry_exceeded block specified." }
192
+ end
193
+ end
194
+
195
+ def call_on_retry_block(message)
196
+ if @on_retry_block
197
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry block." }
198
+ begin
199
+ @on_retry_block.call(message, abort)
200
+ rescue => e
201
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry block. Error: #{e}")
202
+ end
203
+ else
204
+ EventQ.logger.debug { "[#{self.class}] - No on_retry block specified." }
205
+ end
206
+ end
207
+
208
+ def stop
209
+ EventQ.logger.info("[#{self.class}] - Stopping.")
210
+ @is_running = false
211
+ @threads.each { |thr| thr.join }
212
+ return true
213
+ end
214
+
215
+ def on_retry_exceeded(&block)
216
+ @retry_exceeded_block = block
217
+ end
218
+
219
+ def on_retry(&block)
220
+ @on_retry_block = block
221
+ return nil
222
+ end
223
+
224
+ def on_error(&block)
225
+ @on_error_block = block
226
+ return nil
227
+ end
228
+
229
+ def running?
230
+ return @is_running
231
+ end
232
+
233
+ def deserialize_message(payload)
234
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
235
+ return provider.deserialize(payload)
236
+ end
237
+
238
+ def serialize_message(msg)
239
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
240
+ return provider.serialize(msg)
241
+ end
242
+
243
+ private
244
+
245
+ def process_message(response, client, queue, q, block)
246
+ msg = response.messages[0]
247
+ retry_attempts = msg.attributes[APPROXIMATE_RECEIVE_COUNT].to_i - 1
248
+
249
+ #deserialize the message payload
250
+ payload = JSON.load(msg.body)
251
+ message = deserialize_message(payload[MESSAGE])
252
+
253
+ message_args = EventQ::MessageArgs.new(type: message.type,
254
+ retry_attempts: retry_attempts,
255
+ context: message.context,
256
+ content_type: message.content_type)
257
+
258
+ EventQ.logger.info("[#{self.class}] - Message received. Retry Attempts: #{retry_attempts}")
259
+
260
+ @signature_provider_manager.validate_signature(message: message, queue: queue)
261
+
262
+ if(!EventQ::NonceManager.is_allowed?(message.id))
263
+ EventQ.logger.info("[#{self.class}] - Duplicate Message received. Dropping message.")
264
+ client.sqs.delete_message({ queue_url: q, receipt_handle: msg.receipt_handle })
265
+ return false
266
+ end
267
+
268
+ #begin worker block for queue message
269
+ begin
270
+
271
+ block.call(message.content, message_args)
272
+
273
+ if message_args.abort == true
274
+ EventQ.logger.info("[#{self.class}] - Message aborted.")
275
+ else
276
+ #accept the message as processed
277
+ client.sqs.delete_message({ queue_url: q, receipt_handle: msg.receipt_handle })
278
+ EventQ.logger.info("[#{self.class}] - Message acknowledged.")
279
+ end
280
+
281
+ rescue => e
282
+ EventQ.logger.error("[#{self.class}] - An unhandled error happened while attempting to process a queue message. Error: #{e} | Backtrace: #{e.backtrace}")
283
+ error = true
284
+ call_on_error_block(error: e, message: message)
285
+ end
286
+
287
+ if message_args.abort || error
288
+ EventQ::NonceManager.failed(message.id)
289
+ reject_message(queue, client, msg, q, retry_attempts, message, message_args.abort)
290
+ else
291
+ EventQ::NonceManager.complete(message.id)
292
+ end
293
+
294
+ return true
295
+ end
296
+
297
+ def reject_message(queue, client, msg, q, retry_attempts, message, abort)
298
+
299
+ if !queue.allow_retry || retry_attempts >= queue.max_retry_attempts
300
+
301
+ EventQ.logger.info("[#{self.class}] - Message rejected removing from queue. Message: #{serialize_message(message)}")
302
+
303
+ #remove the message from the queue so that it does not get retried again
304
+ client.sqs.delete_message({ queue_url: q, receipt_handle: msg.receipt_handle })
305
+
306
+ if retry_attempts >= queue.max_retry_attempts
307
+
308
+ EventQ.logger.info("[#{self.class}] - Message retry attempt limit exceeded.")
309
+ call_on_retry_exceeded_block(message)
310
+
311
+ end
312
+
313
+ elsif queue.allow_retry
314
+
315
+ retry_attempts += 1
316
+
317
+ EventQ.logger.info("[#{self.class}] - Message rejected requesting retry. Attempts: #{retry_attempts}")
318
+
319
+ if queue.allow_retry_back_off == true
320
+ EventQ.logger.debug { "[#{self.class}] - Calculating message back off retry delay. Attempts: #{retry_attempts} * Delay: #{queue.retry_delay}" }
321
+ visibility_timeout = (queue.retry_delay * retry_attempts) / 1000
322
+ if visibility_timeout > (queue.max_retry_delay / 1000)
323
+ EventQ.logger.debug { "[#{self.class}] - Max message back off retry delay reached." }
324
+ visibility_timeout = queue.max_retry_delay / 1000
325
+ end
326
+ else
327
+ EventQ.logger.debug { "[#{self.class}] - Setting fixed retry delay for message." }
328
+ visibility_timeout = queue.retry_delay / 1000
329
+ end
330
+
331
+ if visibility_timeout > 43200
332
+ EventQ.logger.debug { "[#{self.class}] - AWS max visibility timeout of 12 hours has been exceeded. Setting message retry delay to 12 hours." }
333
+ visibility_timeout = 43200
334
+ end
335
+
336
+ EventQ.logger.debug { "[#{self.class}] - Sending message for retry. Message TTL: #{visibility_timeout}" }
337
+ client.sqs.change_message_visibility({
338
+ queue_url: q, # required
339
+ receipt_handle: msg.receipt_handle, # required
340
+ visibility_timeout: visibility_timeout.to_s, # required
341
+ })
342
+
343
+ call_on_retry_block(message)
344
+
345
+ end
346
+
347
+ end
348
+
349
+ def configure(queue, options = {})
350
+
351
+ @queue = queue
352
+
353
+ #default thread count
354
+ @thread_count = 5
355
+ if options.key?(:thread_count)
356
+ @thread_count = options[:thread_count]
357
+ end
358
+
359
+ #default sleep time in seconds
360
+ @sleep = 5
361
+ if options.key?(:sleep)
362
+ @sleep = options[:sleep]
363
+ end
364
+
365
+ @fork_count = 1
366
+ if options.key?(:fork_count)
367
+ @fork_count = options[:fork_count]
368
+ end
369
+
370
+ if options.key?(:gc_flush_interval)
371
+ @gc_flush_interval = options[:gc_flush_interval]
372
+ end
373
+
374
+ if options.key?(:queue_poll_wait)
375
+ @queue_poll_wait = options[:queue_poll_wait]
376
+ end
377
+
378
+ EventQ.logger.info("[#{self.class}] - Configuring. Process Count: #{@fork_count} | Thread Count: #{@thread_count} | Interval Sleep: #{@sleep} | GC Flush Interval: #{@gc_flush_interval} | Queue Poll Wait: #{@queue_poll_wait}.")
379
+
380
+ return true
381
+
382
+ end
383
+
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,25 @@
1
+ module EventQ
2
+ module Amazon
3
+ class StatusChecker
4
+
5
+ def initialize(queue_manager:, client:)
6
+
7
+ if queue_manager == nil
8
+ raise 'queue_manager must be specified.'.freeze
9
+ end
10
+
11
+ @queue_manager = queue_manager
12
+
13
+ end
14
+
15
+ def queue?(queue)
16
+ @queue_manager.queue_exists?(queue)
17
+ end
18
+
19
+ def event_type?(event_type)
20
+ @queue_manager.topic_exists?(event_type)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ module EventQ
2
+ module Amazon
3
+ class SubscriptionManager
4
+
5
+ def initialize(options)
6
+
7
+ if options[:client] == nil
8
+ raise "[#{self.class}] - :client (QueueClient) must be specified."
9
+ end
10
+
11
+ @client = options[:client]
12
+
13
+ if options[:queue_manager] == nil
14
+ raise "[#{self.class}] - :queue_manager (QueueManager) must be specified."
15
+ end
16
+
17
+ @manager = options[:queue_manager]
18
+ end
19
+
20
+ def subscribe(event_type, queue)
21
+
22
+ topic_arn = @client.create_topic_arn(event_type)
23
+
24
+ q = @manager.get_queue(queue)
25
+ queue_arn = @client.get_queue_arn(queue)
26
+
27
+ @client.sqs.set_queue_attributes({
28
+ queue_url: q,
29
+ attributes:{
30
+ 'Policy'.freeze => '{
31
+ "Version": "2012-10-17",
32
+ "Id": "SNStoSQS",
33
+ "Statement": [
34
+ {
35
+ "Sid":"rule1",
36
+ "Effect": "Allow",
37
+ "Principal": "*",
38
+ "Action": "sqs:*",
39
+ "Resource": "' + queue_arn + '"
40
+ }
41
+ ]
42
+ }'
43
+ }
44
+ })
45
+
46
+ @client.sns.subscribe({
47
+ topic_arn: topic_arn,
48
+ protocol: 'sqs'.freeze,
49
+ endpoint: queue_arn
50
+ })
51
+ EventQ.logger.debug do
52
+ "[#{self.class} #subscribe] - Subscribing Queue: #{queue.name} to topic_arn: #{topic_arn}, endpoint: #{queue_arn}"
53
+ end
54
+ return true
55
+
56
+ end
57
+
58
+ def unsubscribe(queue)
59
+
60
+ raise "[#{self.class}] - Not implemented. Please unsubscribe the queue from the topic inside the AWS Management Console."
61
+
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,370 @@
1
+ require 'java'
2
+ java_import java.util.concurrent.Executors
3
+ module EventQ
4
+ module Amazon
5
+ class QueueWorker
6
+ include EventQ::WorkerId
7
+
8
+ APPROXIMATE_RECEIVE_COUNT = 'ApproximateReceiveCount'.freeze
9
+ MESSAGE = 'Message'.freeze
10
+
11
+ attr_accessor :is_running
12
+
13
+ def initialize
14
+ @is_running = false
15
+
16
+ @on_retry_exceeded_block = nil
17
+ @on_retry_block = nil
18
+ @on_error_block = nil
19
+
20
+ @hash_helper = HashKit::Helper.new
21
+ @serialization_provider_manager = EventQ::SerializationProviders::Manager.new
22
+ @signature_provider_manager = EventQ::SignatureProviders::Manager.new
23
+
24
+ @last_gc_flush = Time.now
25
+ @gc_flush_interval = 10
26
+
27
+ @queue_poll_wait = 10
28
+ end
29
+
30
+ def start(queue, options = {}, &block)
31
+
32
+ EventQ.logger.info("[#{self.class}] - Preparing to start listening for messages.")
33
+
34
+ configure(queue, options)
35
+
36
+ if options[:client] == nil
37
+ raise "[#{self.class}] - :client (QueueClient) must be specified."
38
+ end
39
+
40
+ raise "[#{self.class}] - Worker is already running." if running?
41
+
42
+ client = options[:client]
43
+ EventQ.logger.debug do
44
+ "[#{self.class} #start] - Listening for messages on queue: #{queue.name}, Queue Url: #{client.get_queue_url(queue)}, Queue arn: #{client.get_queue_arn(queue)}"
45
+ end
46
+
47
+ EventQ.logger.info("[#{self.class}] - Listening for messages.")
48
+
49
+ start_process(options, queue, block)
50
+
51
+ return true
52
+ end
53
+
54
+ def start_process(options, queue, block)
55
+
56
+ %w'INT TERM'.each do |sig|
57
+ Signal.trap(sig) {
58
+ stop
59
+ exit
60
+ }
61
+ end
62
+
63
+ @is_running = true
64
+
65
+ @executor = java.util.concurrent.Executors::newFixedThreadPool @thread_count
66
+
67
+ #loop through each thread count
68
+ @thread_count.times do
69
+
70
+ @executor.execute do
71
+
72
+ client = options[:client]
73
+ manager = EventQ::Amazon::QueueManager.new({ client: client })
74
+
75
+ #begin the queue loop for this thread
76
+ while true do
77
+
78
+ #check if the worker is still allowed to run and break out of thread loop if not
79
+ unless running?
80
+ break
81
+ end
82
+
83
+ if @executor.is_shutdown
84
+ break
85
+ end
86
+
87
+ has_message_received = thread_process_iteration(client, manager, queue, block)
88
+
89
+ gc_flush
90
+
91
+ if !has_message_received
92
+ EventQ.logger.debug { "[#{self.class}] - No message received." }
93
+ if @sleep > 0
94
+ EventQ.logger.debug { "[#{self.class}] - Sleeping for #{@sleep} seconds" }
95
+ sleep(@sleep)
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+
105
+ if options.key?(:wait) && options[:wait] == true
106
+ while running? do end
107
+ end
108
+
109
+ end
110
+
111
+ def gc_flush
112
+ if Time.now - last_gc_flush > @gc_flush_interval
113
+ GC.start
114
+ @last_gc_flush = Time.now
115
+ end
116
+ end
117
+
118
+ def last_gc_flush
119
+ @last_gc_flush
120
+ end
121
+
122
+ def thread_process_iteration(client, manager, queue, block)
123
+ #get the queue
124
+ q = manager.get_queue(queue)
125
+
126
+ received = false
127
+
128
+ begin
129
+
130
+ #request a message from the queue
131
+ response = client.sqs.receive_message({
132
+ queue_url: q,
133
+ max_number_of_messages: 1,
134
+ wait_time_seconds: @queue_poll_wait,
135
+ attribute_names: [APPROXIMATE_RECEIVE_COUNT]
136
+ })
137
+
138
+ #check that a message was received
139
+ if response.messages.length > 0
140
+ received = true
141
+ begin
142
+ tag_processing_thread
143
+ process_message(response, client, queue, q, block)
144
+ ensure
145
+ untag_processing_thread
146
+ end
147
+
148
+ end
149
+
150
+ rescue => e
151
+ EventQ.log(:error, "[#{self.class}] - An unhandled error occurred. Error: #{e} | Backtrace: #{e.backtrace}")
152
+ call_on_error_block(error: e)
153
+ end
154
+
155
+ return received
156
+ end
157
+
158
+ def call_on_error_block(error:, message: nil)
159
+ if @on_error_block
160
+ EventQ.logger.debug { "[#{self.class}] - Executing on_error block." }
161
+ begin
162
+ @on_error_block.call(error, message)
163
+ rescue => e
164
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_error block. Error: #{e}")
165
+ end
166
+ else
167
+ EventQ.logger.debug { "[#{self.class}] - No on_error block specified to execute." }
168
+ end
169
+ end
170
+
171
+ def call_on_retry_exceeded_block(message)
172
+ if @on_retry_exceeded_block != nil
173
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry_exceeded block." }
174
+ begin
175
+ @on_retry_exceeded_block.call(message)
176
+ rescue => e
177
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry_exceeded block. Error: #{e}")
178
+ end
179
+ else
180
+ EventQ.logger.debug { "[#{self.class}] - No on_retry_exceeded block specified." }
181
+ end
182
+ end
183
+
184
+ def call_on_retry_block(message)
185
+ if @on_retry_block
186
+ EventQ.logger.debug { "[#{self.class}] - Executing on_retry block." }
187
+ begin
188
+ @on_retry_block.call(message, abort)
189
+ rescue => e
190
+ EventQ.logger.error("[#{self.class}] - An error occurred executing the on_retry block. Error: #{e}")
191
+ end
192
+ else
193
+ EventQ.logger.debug { "[#{self.class}] - No on_retry block specified." }
194
+ end
195
+ end
196
+
197
+ def stop
198
+ EventQ.logger.info("[#{self.class}] - Stopping.")
199
+ @is_running = false
200
+ @executor.shutdown
201
+ return true
202
+ end
203
+
204
+ def on_retry_exceeded(&block)
205
+ @retry_exceeded_block = block
206
+ end
207
+
208
+ def on_retry(&block)
209
+ @on_retry_block = block
210
+ return nil
211
+ end
212
+
213
+ def on_error(&block)
214
+ @on_error_block = block
215
+ return nil
216
+ end
217
+
218
+ def running?
219
+ return @is_running
220
+ end
221
+
222
+ def deserialize_message(payload)
223
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
224
+ return provider.deserialize(payload)
225
+ end
226
+
227
+ def serialize_message(msg)
228
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
229
+ return provider.serialize(msg)
230
+ end
231
+
232
+ private
233
+
234
+ def process_message(response, client, queue, q, block)
235
+ msg = response.messages[0]
236
+ retry_attempts = msg.attributes[APPROXIMATE_RECEIVE_COUNT].to_i - 1
237
+
238
+ #deserialize the message payload
239
+ payload = JSON.load(msg.body)
240
+ message = deserialize_message(payload[MESSAGE])
241
+
242
+ message_args = EventQ::MessageArgs.new(type: message.type,
243
+ retry_attempts: retry_attempts,
244
+ context: message.context,
245
+ content_type: message.content_type)
246
+
247
+ EventQ.logger.info("[#{self.class}] - Message received. Retry Attempts: #{retry_attempts}")
248
+
249
+ @signature_provider_manager.validate_signature(message: message, queue: queue)
250
+
251
+ if(!EventQ::NonceManager.is_allowed?(message.id))
252
+ EventQ.logger.info("[#{self.class}] - Duplicate Message received. Dropping message.")
253
+ client.sqs.delete_message({ queue_url: q, receipt_handle: msg.receipt_handle })
254
+ return false
255
+ end
256
+
257
+ #begin worker block for queue message
258
+ begin
259
+
260
+ block.call(message.content, message_args)
261
+
262
+ if message_args.abort == true
263
+ EventQ.logger.info("[#{self.class}] - Message aborted.")
264
+ else
265
+ #accept the message as processed
266
+ client.sqs.delete_message({ queue_url: q, receipt_handle: msg.receipt_handle })
267
+ EventQ.logger.info("[#{self.class}] - Message acknowledged.")
268
+ end
269
+
270
+ rescue => e
271
+ EventQ.logger.error("[#{self.class}] - An unhandled error happened while attempting to process a queue message. Error: #{e} | Backtrace: #{e.backtrace}")
272
+ error = true
273
+ call_on_error_block(error: e, message: message)
274
+ end
275
+
276
+ if message_args.abort || error
277
+ EventQ::NonceManager.failed(message.id)
278
+ reject_message(queue, client, msg, q, retry_attempts, message, message_args.abort)
279
+ else
280
+ EventQ::NonceManager.complete(message.id)
281
+ end
282
+
283
+ return true
284
+ end
285
+
286
+ def reject_message(queue, client, msg, q, retry_attempts, message, abort)
287
+
288
+ if !queue.allow_retry || retry_attempts >= queue.max_retry_attempts
289
+
290
+ EventQ.logger.info("[#{self.class}] - Message rejected removing from queue. Message: #{serialize_message(message)}")
291
+
292
+ #remove the message from the queue so that it does not get retried again
293
+ client.sqs.delete_message({ queue_url: q, receipt_handle: msg.receipt_handle })
294
+
295
+ if retry_attempts >= queue.max_retry_attempts
296
+
297
+ EventQ.logger.info("[#{self.class}] - Message retry attempt limit exceeded.")
298
+ call_on_retry_exceeded_block(message)
299
+
300
+ end
301
+
302
+ elsif queue.allow_retry
303
+
304
+ retry_attempts += 1
305
+
306
+ EventQ.logger.info("[#{self.class}] - Message rejected requesting retry. Attempts: #{retry_attempts}")
307
+
308
+ if queue.allow_retry_back_off == true
309
+ EventQ.logger.debug { "[#{self.class}] - Calculating message back off retry delay. Attempts: #{retry_attempts} * Delay: #{queue.retry_delay}" }
310
+ visibility_timeout = (queue.retry_delay * retry_attempts) / 1000
311
+ if visibility_timeout > (queue.max_retry_delay / 1000)
312
+ EventQ.logger.debug { "[#{self.class}] - Max message back off retry delay reached." }
313
+ visibility_timeout = queue.max_retry_delay / 1000
314
+ end
315
+ else
316
+ EventQ.logger.debug { "[#{self.class}] - Setting fixed retry delay for message." }
317
+ visibility_timeout = queue.retry_delay / 1000
318
+ end
319
+
320
+ if visibility_timeout > 43200
321
+ EventQ.logger.debug { "[#{self.class}] - AWS max visibility timeout of 12 hours has been exceeded. Setting message retry delay to 12 hours." }
322
+ visibility_timeout = 43200
323
+ end
324
+
325
+ EventQ.logger.debug { "[#{self.class}] - Sending message for retry. Message TTL: #{visibility_timeout}" }
326
+ client.sqs.change_message_visibility({
327
+ queue_url: q, # required
328
+ receipt_handle: msg.receipt_handle, # required
329
+ visibility_timeout: visibility_timeout.to_s, # required
330
+ })
331
+
332
+ call_on_retry_block(message)
333
+
334
+ end
335
+
336
+ end
337
+
338
+ def configure(queue, options = {})
339
+
340
+ @queue = queue
341
+
342
+ #default thread count
343
+ @thread_count = 5
344
+ if options.key?(:thread_count)
345
+ @thread_count = options[:thread_count]
346
+ end
347
+
348
+ #default sleep time in seconds
349
+ @sleep = 5
350
+ if options.key?(:sleep)
351
+ @sleep = options[:sleep]
352
+ end
353
+
354
+ if options.key?(:gc_flush_interval)
355
+ @gc_flush_interval = options[:gc_flush_interval]
356
+ end
357
+
358
+ if options.key?(:queue_poll_wait)
359
+ @queue_poll_wait = options[:queue_poll_wait]
360
+ end
361
+
362
+ EventQ.logger.info("[#{self.class}] - Configuring. Thread Count: #{@thread_count} | Interval Sleep: #{@sleep} | GC Flush Interval: #{@gc_flush_interval} | Queue Poll Wait: #{@queue_poll_wait}.")
363
+
364
+ return true
365
+
366
+ end
367
+
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,5 @@
1
+ module EventQ
2
+ module Amazon
3
+ VERSION = "1.14.0"
4
+ end
5
+ end
data/lib/eventq_aws.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'aws-sdk-core'
2
+ require 'eventq_base'
3
+
4
+ require 'eventq_aws/version'
5
+ require 'eventq_aws/aws_eventq_client'
6
+ require 'eventq_aws/aws_queue_client'
7
+ require 'eventq_aws/aws_queue_manager'
8
+ require 'eventq_aws/aws_subscription_manager'
9
+ require_relative 'eventq_aws/aws_status_checker'
10
+
11
+ if RUBY_PLATFORM =~ /java/
12
+ require 'eventq_aws/jruby/aws_queue_worker'
13
+ else
14
+ require 'eventq_aws/aws_queue_worker'
15
+ end
16
+
17
+ module EventQ
18
+ def self.namespace
19
+ @namespace
20
+ end
21
+ def self.namespace=(value)
22
+ @namespace = value
23
+ end
24
+ def self.create_event_type(event_type)
25
+ if EventQ.namespace == nil
26
+ return event_type
27
+ end
28
+ return "#{EventQ.namespace}-#{event_type}"
29
+ end
30
+ def self.create_queue_name(queue_name)
31
+ if EventQ.namespace == nil
32
+ return queue_name
33
+ end
34
+ return "#{EventQ.namespace}-#{queue_name}"
35
+ end
36
+ end
37
+
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eventq_aws
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.14.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: '0'
61
+ name: aws-sdk-core
62
+ prerelease: false
63
+ type: :runtime
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.15'
75
+ name: eventq_base
76
+ prerelease: false
77
+ type: :runtime
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.15'
83
+ description: This is the aws 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_aws.rb
93
+ - lib/eventq_aws/aws_eventq_client.rb
94
+ - lib/eventq_aws/aws_queue_client.rb
95
+ - lib/eventq_aws/aws_queue_manager.rb
96
+ - lib/eventq_aws/aws_queue_worker.rb
97
+ - lib/eventq_aws/aws_status_checker.rb
98
+ - lib/eventq_aws/aws_subscription_manager.rb
99
+ - lib/eventq_aws/jruby/aws_queue_worker.rb
100
+ - lib/eventq_aws/version.rb
101
+ homepage: https://github.com/vaughanbrittonsage/eventq
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.6.11
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: This is the aws implementation for EventQ
125
+ test_files: []