eventq 2.0.0.rc1

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.
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