eventq_aws 1.14.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: 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: []