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.
- checksums.yaml +7 -0
- data/README.md +336 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/eventq/aws.rb +38 -0
- data/lib/eventq/eventq_aws/README.md +53 -0
- data/lib/eventq/eventq_aws/aws_eventq_client.rb +120 -0
- data/lib/eventq/eventq_aws/aws_queue_client.rb +64 -0
- data/lib/eventq/eventq_aws/aws_queue_manager.rb +68 -0
- data/lib/eventq/eventq_aws/aws_queue_worker.rb +168 -0
- data/lib/eventq/eventq_aws/aws_status_checker.rb +25 -0
- data/lib/eventq/eventq_aws/aws_subscription_manager.rb +65 -0
- data/lib/eventq/eventq_aws/jruby/aws_queue_worker.rb +370 -0
- data/lib/eventq/eventq_aws/sns.rb +64 -0
- data/lib/eventq/eventq_aws/sqs.rb +112 -0
- data/lib/eventq/eventq_base/configuration.rb +33 -0
- data/lib/eventq/eventq_base/event_raised_exchange.rb +7 -0
- data/lib/eventq/eventq_base/event_raised_queue.rb +7 -0
- data/lib/eventq/eventq_base/eventq_client_contract.rb +9 -0
- data/lib/eventq/eventq_base/eventq_logger.rb +28 -0
- data/lib/eventq/eventq_base/exceptions/invalid_signature_exception.rb +9 -0
- data/lib/eventq/eventq_base/exceptions/worker_thread_error.rb +10 -0
- data/lib/eventq/eventq_base/exceptions.rb +2 -0
- data/lib/eventq/eventq_base/exchange.rb +5 -0
- data/lib/eventq/eventq_base/message_args.rb +23 -0
- data/lib/eventq/eventq_base/nonce_manager.rb +57 -0
- data/lib/eventq/eventq_base/queue.rb +27 -0
- data/lib/eventq/eventq_base/queue_message.rb +31 -0
- data/lib/eventq/eventq_base/queue_worker_contract.rb +23 -0
- data/lib/eventq/eventq_base/serialization_providers/binary_serialization_provider.rb +15 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/array_writer.rb +20 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/attribute_writer.rb +24 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/class_writer.rb +20 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/date_time_writer.rb +33 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/date_writer.rb +22 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/hash_writer.rb +18 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/rational_writer.rb +20 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/serializer.rb +17 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/time_writer.rb +18 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj/value_writer.rb +16 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj.rb +10 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby/oj_serialization_provider.rb +25 -0
- data/lib/eventq/eventq_base/serialization_providers/jruby.rb +2 -0
- data/lib/eventq/eventq_base/serialization_providers/json_serialization_provider.rb +28 -0
- data/lib/eventq/eventq_base/serialization_providers/oj_serialization_provider.rb +24 -0
- data/lib/eventq/eventq_base/serialization_providers.rb +36 -0
- data/lib/eventq/eventq_base/signature_providers/sha256_signature_provider.rb +31 -0
- data/lib/eventq/eventq_base/signature_providers.rb +44 -0
- data/lib/eventq/eventq_base/subscription_manager_contract.rb +13 -0
- data/lib/eventq/eventq_base/version.rb +3 -0
- data/lib/eventq/eventq_base/worker_id.rb +20 -0
- data/lib/eventq/eventq_rabbitmq/README.md +36 -0
- data/lib/eventq/eventq_rabbitmq/default_queue.rb +12 -0
- data/lib/eventq/eventq_rabbitmq/jruby/rabbitmq_queue_worker.rb +367 -0
- data/lib/eventq/eventq_rabbitmq/rabbitmq_eventq_client.rb +140 -0
- data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_client.rb +54 -0
- data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_manager.rb +104 -0
- data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_worker.rb +168 -0
- data/lib/eventq/eventq_rabbitmq/rabbitmq_status_checker.rb +62 -0
- data/lib/eventq/eventq_rabbitmq/rabbitmq_subscription_manager.rb +54 -0
- data/lib/eventq/queue_worker.rb +241 -0
- data/lib/eventq/rabbitmq.rb +49 -0
- data/lib/eventq/worker_status.rb +64 -0
- data/lib/eventq.rb +25 -0
- 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
|