eventq 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +336 -0
  3. data/bin/console +14 -0
  4. data/bin/setup +8 -0
  5. data/lib/eventq/aws.rb +38 -0
  6. data/lib/eventq/eventq_aws/README.md +53 -0
  7. data/lib/eventq/eventq_aws/aws_eventq_client.rb +120 -0
  8. data/lib/eventq/eventq_aws/aws_queue_client.rb +64 -0
  9. data/lib/eventq/eventq_aws/aws_queue_manager.rb +68 -0
  10. data/lib/eventq/eventq_aws/aws_queue_worker.rb +168 -0
  11. data/lib/eventq/eventq_aws/aws_status_checker.rb +25 -0
  12. data/lib/eventq/eventq_aws/aws_subscription_manager.rb +65 -0
  13. data/lib/eventq/eventq_aws/jruby/aws_queue_worker.rb +370 -0
  14. data/lib/eventq/eventq_aws/sns.rb +64 -0
  15. data/lib/eventq/eventq_aws/sqs.rb +112 -0
  16. data/lib/eventq/eventq_base/configuration.rb +33 -0
  17. data/lib/eventq/eventq_base/event_raised_exchange.rb +7 -0
  18. data/lib/eventq/eventq_base/event_raised_queue.rb +7 -0
  19. data/lib/eventq/eventq_base/eventq_client_contract.rb +9 -0
  20. data/lib/eventq/eventq_base/eventq_logger.rb +28 -0
  21. data/lib/eventq/eventq_base/exceptions/invalid_signature_exception.rb +9 -0
  22. data/lib/eventq/eventq_base/exceptions/worker_thread_error.rb +10 -0
  23. data/lib/eventq/eventq_base/exceptions.rb +2 -0
  24. data/lib/eventq/eventq_base/exchange.rb +5 -0
  25. data/lib/eventq/eventq_base/message_args.rb +23 -0
  26. data/lib/eventq/eventq_base/nonce_manager.rb +57 -0
  27. data/lib/eventq/eventq_base/queue.rb +27 -0
  28. data/lib/eventq/eventq_base/queue_message.rb +31 -0
  29. data/lib/eventq/eventq_base/queue_worker_contract.rb +23 -0
  30. data/lib/eventq/eventq_base/serialization_providers/binary_serialization_provider.rb +15 -0
  31. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/array_writer.rb +20 -0
  32. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/attribute_writer.rb +24 -0
  33. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/class_writer.rb +20 -0
  34. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/date_time_writer.rb +33 -0
  35. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/date_writer.rb +22 -0
  36. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/hash_writer.rb +18 -0
  37. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/rational_writer.rb +20 -0
  38. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/serializer.rb +17 -0
  39. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/time_writer.rb +18 -0
  40. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/value_writer.rb +16 -0
  41. data/lib/eventq/eventq_base/serialization_providers/jruby/oj.rb +10 -0
  42. data/lib/eventq/eventq_base/serialization_providers/jruby/oj_serialization_provider.rb +25 -0
  43. data/lib/eventq/eventq_base/serialization_providers/jruby.rb +2 -0
  44. data/lib/eventq/eventq_base/serialization_providers/json_serialization_provider.rb +28 -0
  45. data/lib/eventq/eventq_base/serialization_providers/oj_serialization_provider.rb +24 -0
  46. data/lib/eventq/eventq_base/serialization_providers.rb +36 -0
  47. data/lib/eventq/eventq_base/signature_providers/sha256_signature_provider.rb +31 -0
  48. data/lib/eventq/eventq_base/signature_providers.rb +44 -0
  49. data/lib/eventq/eventq_base/subscription_manager_contract.rb +13 -0
  50. data/lib/eventq/eventq_base/version.rb +3 -0
  51. data/lib/eventq/eventq_base/worker_id.rb +20 -0
  52. data/lib/eventq/eventq_rabbitmq/README.md +36 -0
  53. data/lib/eventq/eventq_rabbitmq/default_queue.rb +12 -0
  54. data/lib/eventq/eventq_rabbitmq/jruby/rabbitmq_queue_worker.rb +367 -0
  55. data/lib/eventq/eventq_rabbitmq/rabbitmq_eventq_client.rb +140 -0
  56. data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_client.rb +54 -0
  57. data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_manager.rb +104 -0
  58. data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_worker.rb +168 -0
  59. data/lib/eventq/eventq_rabbitmq/rabbitmq_status_checker.rb +62 -0
  60. data/lib/eventq/eventq_rabbitmq/rabbitmq_subscription_manager.rb +54 -0
  61. data/lib/eventq/queue_worker.rb +241 -0
  62. data/lib/eventq/rabbitmq.rb +49 -0
  63. data/lib/eventq/worker_status.rb +64 -0
  64. data/lib/eventq.rb +25 -0
  65. metadata +289 -0
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventQ
4
+ module Amazon
5
+ class QueueManager
6
+
7
+ VISIBILITY_TIMEOUT = 'VisibilityTimeout'
8
+ MESSAGE_RETENTION_PERIOD = 'MessageRetentionPeriod'
9
+
10
+ def initialize(options)
11
+ mandatory = [:client]
12
+ missing = mandatory - options.keys
13
+ raise "[#{self.class}] - Missing options #{missing} must be specified." unless missing.empty?
14
+
15
+ @client = options[:client]
16
+ @visibility_timeout = options[:visibility_timeout] || 300 #5 minutes
17
+ @message_retention_period = options[:message_retention_period] || 1209600 #14 days (max aws value)
18
+
19
+ end
20
+
21
+ def get_queue(queue)
22
+ if queue.dlq
23
+ queue_exists?(queue.dlq) ? update_queue(queue.dlq) : create_queue(queue.dlq)
24
+ end
25
+
26
+ queue_exists?(queue) ? update_queue(queue) : create_queue(queue)
27
+ end
28
+
29
+ def create_queue(queue)
30
+ @client.sqs_helper.create_queue(queue, queue_attributes(queue))
31
+ end
32
+
33
+ def drop_queue(queue)
34
+ @client.sqs_helper.drop_queue(queue)
35
+ end
36
+
37
+ def drop_topic(event_type)
38
+ @client.sns_helper.drop_topic(event_type)
39
+ end
40
+
41
+ def topic_exists?(event_type)
42
+ !!@client.sns_helper.get_topic_arn(event_type)
43
+ end
44
+
45
+ def queue_exists?(queue)
46
+ !!@client.sqs_helper.get_queue_url(queue)
47
+ end
48
+
49
+ def update_queue(queue)
50
+ @client.sqs_helper.update_queue(queue, queue_attributes(queue))
51
+ end
52
+
53
+ def queue_attributes(queue)
54
+ attributes = {
55
+ VISIBILITY_TIMEOUT => @visibility_timeout.to_s,
56
+ MESSAGE_RETENTION_PERIOD => @message_retention_period.to_s
57
+ }
58
+
59
+ if queue.dlq
60
+ dlq_arn = @client.sqs_helper.get_queue_arn(queue.dlq)
61
+ attributes['RedrivePolicy'] = %Q({"maxReceiveCount":"#{queue.max_receive_count}","deadLetterTargetArn":"#{dlq_arn}"})
62
+ end
63
+
64
+ attributes
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk'
4
+
5
+ module EventQ
6
+ module Amazon
7
+ class QueueWorker
8
+ include EventQ::WorkerId
9
+
10
+ APPROXIMATE_RECEIVE_COUNT = 'ApproximateReceiveCount'
11
+ MESSAGE = 'Message'
12
+
13
+ attr_accessor :context
14
+
15
+ def initialize
16
+ @serialization_provider_manager = EventQ::SerializationProviders::Manager.new
17
+ @signature_provider_manager = EventQ::SignatureProviders::Manager.new
18
+ end
19
+
20
+ def pre_process(context, options)
21
+ # don't do anything specific to set up the process before threads are fired off.
22
+ end
23
+
24
+ def thread_process_iteration(queue, options, block)
25
+ client = options[:client]
26
+ manager = options[:manager] || EventQ::Amazon::QueueManager.new({ client: client })
27
+
28
+ # get the queue
29
+ queue_url = manager.get_queue(queue)
30
+ poller = Aws::SQS::QueuePoller.new(queue_url, attribute_names: [APPROXIMATE_RECEIVE_COUNT])
31
+
32
+ # Polling will block indefinitely unless we force it to stop
33
+ poller.before_request do |stats|
34
+ unless context.running?
35
+ EventQ.logger.info("AWS Poller shutting down")
36
+ throw :stop_polling
37
+ end
38
+ end
39
+
40
+ poller.poll(skip_delete: true, wait_time_seconds: options[:queue_poll_wait]) do |msg, stats|
41
+ begin
42
+ tag_processing_thread
43
+ process_message(msg, poller, queue, block)
44
+ rescue => e
45
+ EventQ.logger.error do
46
+ "[#{self.class}] - An unhandled error occurred. Error: #{e} | Backtrace: #{e.backtrace}"
47
+ end
48
+ context.call_on_error_block(error: e)
49
+ ensure
50
+ untag_processing_thread
51
+ end
52
+ end
53
+ end
54
+
55
+ def deserialize_message(payload)
56
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
57
+ provider.deserialize(payload)
58
+ end
59
+
60
+ def serialize_message(msg)
61
+ provider = @serialization_provider_manager.get_provider(EventQ::Configuration.serialization_provider)
62
+ provider.serialize(msg)
63
+ end
64
+
65
+ def configure(options = {})
66
+ options[:queue_poll_wait] ||= 10
67
+
68
+ EventQ.logger.info("[#{self.class}] - Configuring. Queue Poll Wait: #{options[:queue_poll_wait]}")
69
+ end
70
+
71
+ private
72
+
73
+ def process_message(msg, poller, queue, block)
74
+ retry_attempts = msg.attributes[APPROXIMATE_RECEIVE_COUNT].to_i - 1
75
+
76
+ # deserialize the message payload
77
+ payload = JSON.load(msg.body)
78
+ message = deserialize_message(payload[MESSAGE])
79
+
80
+ message_args = EventQ::MessageArgs.new(
81
+ type: message.type,
82
+ retry_attempts: retry_attempts,
83
+ context: message.context,
84
+ content_type: message.content_type,
85
+ id: message.id,
86
+ sent: message.created
87
+ )
88
+
89
+ EventQ.logger.info("[#{self.class}] - Message received. Retry Attempts: #{retry_attempts}")
90
+
91
+ @signature_provider_manager.validate_signature(message: message, queue: queue)
92
+
93
+ if(!EventQ::NonceManager.is_allowed?(message.id))
94
+ EventQ.logger.info("[#{self.class}] - Duplicate Message received. Ignoring message.")
95
+ return false
96
+ end
97
+
98
+ # begin worker block for queue message
99
+ begin
100
+ block.call(message.content, message_args)
101
+
102
+ if message_args.abort == true
103
+ EventQ.logger.info("[#{self.class}] - Message aborted.")
104
+ else
105
+ # accept the message as processed
106
+ poller.delete_message(msg)
107
+ EventQ.logger.info("[#{self.class}] - Message acknowledged.")
108
+ end
109
+
110
+ rescue => e
111
+ EventQ.logger.error("[#{self.class}] - unhandled error while attempting to process a queue message")
112
+ EventQ.logger.error(e)
113
+ error = true
114
+ context.call_on_error_block(error: e, message: message)
115
+ end
116
+
117
+ if message_args.abort || error
118
+ EventQ::NonceManager.failed(message.id)
119
+ reject_message(queue, poller, msg, retry_attempts, message, message_args.abort)
120
+ else
121
+ EventQ::NonceManager.complete(message.id)
122
+ end
123
+
124
+ true
125
+ end
126
+
127
+ def reject_message(queue, poller, msg, retry_attempts, message, abort)
128
+ if !queue.allow_retry || retry_attempts >= queue.max_retry_attempts
129
+ EventQ.logger.info("[#{self.class}] - Message rejected removing from queue. Message: #{serialize_message(message)}")
130
+
131
+ # remove the message from the queue so that it does not get retried again
132
+ poller.delete_message(msg)
133
+
134
+ if retry_attempts >= queue.max_retry_attempts
135
+ EventQ.logger.info("[#{self.class}] - Message retry attempt limit exceeded.")
136
+ context.call_on_retry_exceeded_block(message)
137
+ end
138
+ elsif queue.allow_retry
139
+ retry_attempts += 1
140
+
141
+ EventQ.logger.info("[#{self.class}] - Message rejected requesting retry. Attempts: #{retry_attempts}")
142
+
143
+ if queue.allow_retry_back_off == true
144
+ EventQ.logger.debug { "[#{self.class}] - Calculating message back off retry delay. Attempts: #{retry_attempts} * Delay: #{queue.retry_delay}" }
145
+ visibility_timeout = (queue.retry_delay * retry_attempts) / 1000
146
+ if visibility_timeout > (queue.max_retry_delay / 1000)
147
+ EventQ.logger.debug { "[#{self.class}] - Max message back off retry delay reached." }
148
+ visibility_timeout = queue.max_retry_delay / 1000
149
+ end
150
+ else
151
+ EventQ.logger.debug { "[#{self.class}] - Setting fixed retry delay for message." }
152
+ visibility_timeout = queue.retry_delay / 1000
153
+ end
154
+
155
+ if visibility_timeout > 43200
156
+ EventQ.logger.debug { "[#{self.class}] - AWS max visibility timeout of 12 hours has been exceeded. Setting message retry delay to 12 hours." }
157
+ visibility_timeout = 43200
158
+ end
159
+
160
+ EventQ.logger.debug { "[#{self.class}] - Sending message for retry. Message TTL: #{visibility_timeout}" }
161
+ poller.change_message_visibility_timeout(msg, visibility_timeout)
162
+
163
+ context.call_on_retry_block(message)
164
+ end
165
+ end
166
+ end
167
+ end
168
+ 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,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventQ
4
+ module Amazon
5
+ class SubscriptionManager
6
+
7
+ def initialize(options)
8
+ mandatory = [:client, :queue_manager]
9
+ missing = mandatory - options.keys
10
+ raise "[#{self.class}] - Missing options #{missing} must be specified." unless missing.empty?
11
+
12
+ @client = options[:client]
13
+ @manager = options[:queue_manager]
14
+ end
15
+
16
+ def subscribe(event_type, queue)
17
+ topic_arn = @client.sns_helper.create_topic_arn(event_type)
18
+
19
+ q = @manager.get_queue(queue)
20
+ queue_arn = @client.sqs_helper.get_queue_arn(queue)
21
+
22
+ @client.sqs.set_queue_attributes(
23
+ {
24
+ queue_url: q,
25
+ attributes:
26
+ {
27
+ 'Policy' => queue_policy(queue_arn)
28
+ }
29
+ }
30
+ )
31
+
32
+ @client.sns.subscribe({
33
+ topic_arn: topic_arn,
34
+ protocol: 'sqs',
35
+ endpoint: queue_arn
36
+ })
37
+ EventQ.logger.debug do
38
+ "[#{self.class} #subscribe] - Subscribing Queue: #{queue.name} to topic_arn: #{topic_arn}, endpoint: #{queue_arn}"
39
+ end
40
+
41
+ true
42
+ end
43
+
44
+ def unsubscribe(queue)
45
+ raise "[#{self.class}] - Not implemented. Please unsubscribe the queue from the topic inside the AWS Management Console."
46
+ end
47
+
48
+ def queue_policy(queue_arn)
49
+ '{
50
+ "Version": "2012-10-17",
51
+ "Id": "SNStoSQS",
52
+ "Statement": [
53
+ {
54
+ "Sid":"rule1",
55
+ "Effect": "Allow",
56
+ "Principal": "*",
57
+ "Action": "sqs:*",
58
+ "Resource": "' + queue_arn + '"
59
+ }
60
+ ]
61
+ }'
62
+ end
63
+ end
64
+ end
65
+ 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.sqs_helper.get_queue_url(queue)}, Queue arn: #{client.sqs_helper.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