mimi-messaging-sqs_sns 0.7.0 → 0.8.0
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 +4 -4
- data/CHANGELOG.md +13 -0
- data/TODO.md +4 -4
- data/examples/event.rb +3 -3
- data/examples/mass-query.rb +53 -0
- data/examples/responder.rb +6 -1
- data/examples/subscriber.rb +6 -3
- data/lib/mimi/messaging/sqs_sns/adapter.rb +98 -32
- data/lib/mimi/messaging/sqs_sns/consumer.rb +20 -0
- data/lib/mimi/messaging/sqs_sns/reply_consumer.rb +1 -1
- data/lib/mimi/messaging/sqs_sns/timeout_queue.rb +65 -0
- data/lib/mimi/messaging/sqs_sns/version.rb +1 -1
- data/mimi-messaging-sqs_sns.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf7944b9a3e10f154f738d66544cfa63611b17a742b156460969f70821ad86bb
|
4
|
+
data.tar.gz: f26e3e3623208170de192174c9c0fdb2ef5e26fa8cca831e73065eb86e21b0b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37bbb87ba16df4886a5e525ca13732b3712021c5269418d8d8c90e1af125cbaa7e32aa5757a96293569a1717cc4c00542aaf20d12f3fc7514477cd3d708cd615
|
7
|
+
data.tar.gz: 9d53cc0479c47af711f0e89203f9ef1ced767115743fb2fa685ea559ebb0b891226a248219ff2103ec95207b678814c1c0b983f82a22eda8e741feac01a2d4dc
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## UNRELEASED
|
4
|
+
|
5
|
+
## v0.8.0
|
6
|
+
|
7
|
+
* [#3](https://github.com/kukushkin/mimi-messaging-sqs_sns/pull/3)
|
8
|
+
* Added a worker pool:
|
9
|
+
* now processing of messages from a single queue can be done in multiple parallel threads (workers)
|
10
|
+
* the worker threads are shared across all message consumers which read messages from different queues
|
11
|
+
* the size of the worker pool is limited, to have at most `mq_worker_pool_max_threads` processing messages in parallel
|
12
|
+
* `mq_worker_pool_min_threads` determines the target minimal number of threads in the pool, waiting for new messages
|
13
|
+
* `mq_worker_pool_max_backlog` controls how many messages which are read from SQS queues can be put in the worker pool backlog; if a new message is read from SQS queue and the backlog is full, this message is NACK-ed (put back into SQS queue for the other consumers to process)
|
14
|
+
* Improved thread-safety of the adapter: reply consumer, TimeoutQueue#pop
|
15
|
+
|
3
16
|
## v0.7.0
|
4
17
|
|
5
18
|
* [#1](https://github.com/kukushkin/mimi-messaging-sqs_sns/pull/1)
|
data/TODO.md
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
List of missing features planned for future releases.
|
4
4
|
|
5
|
-
[X] Log error and recover if the reply cannot be sent
|
6
|
-
[ ] Threadsafe TimeoutQueue
|
7
|
-
[ ] Multithreaded consumers
|
8
|
-
[ ] Subscribe to topic without a queue (Temporary queues)
|
5
|
+
* [X] Log error and recover if the reply cannot be sent
|
6
|
+
* [ ] Threadsafe TimeoutQueue
|
7
|
+
* [ ] Multithreaded consumers
|
8
|
+
* [ ] Subscribe to topic without a queue (Temporary queues)
|
9
9
|
|
data/examples/event.rb
CHANGED
@@ -4,8 +4,8 @@ require "mimi/messaging/sqs_sns"
|
|
4
4
|
|
5
5
|
COUNT = 10
|
6
6
|
AWS_REGION = "eu-west-1"
|
7
|
-
AWS_SQS_ENDPOINT_URL = "http://localstack:
|
8
|
-
AWS_SNS_ENDPOINT_URL = "http://localstack:
|
7
|
+
AWS_SQS_ENDPOINT_URL = "http://localstack:4566"
|
8
|
+
AWS_SNS_ENDPOINT_URL = "http://localstack:4566"
|
9
9
|
AWS_ACCESS_KEY_ID = "foo"
|
10
10
|
AWS_SECRET_ACCESS_KEY = "bar"
|
11
11
|
|
@@ -30,5 +30,5 @@ COUNT.times do |i|
|
|
30
30
|
t = Time.now
|
31
31
|
puts "Publishing event: #{i}"
|
32
32
|
adapter.event("hello#tested", i: i) # rand(100))
|
33
|
-
sleep 1
|
33
|
+
sleep 0.1
|
34
34
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mimi/messaging/sqs_sns"
|
4
|
+
|
5
|
+
COUNT = 10
|
6
|
+
THREADS = 10
|
7
|
+
QUERY_TIMEOUT = 60
|
8
|
+
AWS_REGION = "eu-west-1"
|
9
|
+
AWS_SQS_ENDPOINT_URL = "http://localstack:4566"
|
10
|
+
AWS_SNS_ENDPOINT_URL = "http://localstack:4566"
|
11
|
+
AWS_ACCESS_KEY_ID = "foo"
|
12
|
+
AWS_SECRET_ACCESS_KEY = "bar"
|
13
|
+
AWS_SQS_SNS_KMS_MASTER_KEY_ID = "blah"
|
14
|
+
|
15
|
+
logger = Logger.new(STDOUT)
|
16
|
+
logger.level = Logger::INFO
|
17
|
+
Mimi::Messaging.use(logger: logger, serializer: Mimi::Messaging::JsonSerializer)
|
18
|
+
Mimi::Messaging.configure(
|
19
|
+
mq_adapter: "sqs_sns",
|
20
|
+
mq_aws_access_key_id: AWS_ACCESS_KEY_ID,
|
21
|
+
mq_aws_secret_access_key: AWS_SECRET_ACCESS_KEY,
|
22
|
+
mq_aws_region: AWS_REGION,
|
23
|
+
mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL,
|
24
|
+
mq_aws_sns_endpoint: AWS_SNS_ENDPOINT_URL,
|
25
|
+
mq_aws_sqs_sns_kms_master_key_id: AWS_SQS_SNS_KMS_MASTER_KEY_ID,
|
26
|
+
mq_default_query_timeout: QUERY_TIMEOUT
|
27
|
+
)
|
28
|
+
adapter = Mimi::Messaging.adapter
|
29
|
+
|
30
|
+
adapter.start
|
31
|
+
|
32
|
+
t_start = Time.now
|
33
|
+
t_queries = []
|
34
|
+
threads = []
|
35
|
+
THREADS.times do |ti|
|
36
|
+
threads << Thread.new do
|
37
|
+
COUNT.times do |i|
|
38
|
+
t = Time.now
|
39
|
+
result = adapter.query("test/hello", i: i) # rand(100))
|
40
|
+
t = Time.now - t
|
41
|
+
t_queries << t
|
42
|
+
puts "result: #{result.to_h}, t: %.3fs" % t
|
43
|
+
sleep 0.1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
threads.each(&:join)
|
49
|
+
|
50
|
+
t_elapsed = Time.now - t_start
|
51
|
+
puts "t_elapsed: %.3fs" % t_elapsed
|
52
|
+
adapter.stop
|
53
|
+
puts "t.avg: %.3fs" % (t_queries.sum / t_queries.count)
|
data/examples/responder.rb
CHANGED
@@ -17,6 +17,7 @@ class Processor
|
|
17
17
|
def self.call_query(method_name, message, opts)
|
18
18
|
# puts "QUERY: #{method_name}, #{message}, headers: #{opts[:headers]}"
|
19
19
|
puts "QUERY: #{method_name}, headers: #{opts[:headers]}"
|
20
|
+
sleep 1 # imitate work
|
20
21
|
{}
|
21
22
|
end
|
22
23
|
end # class Processor
|
@@ -32,7 +33,11 @@ Mimi::Messaging.configure(
|
|
32
33
|
mq_aws_region: AWS_REGION,
|
33
34
|
mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL,
|
34
35
|
mq_aws_sns_endpoint: AWS_SNS_ENDPOINT_URL,
|
35
|
-
mq_aws_sqs_sns_kms_master_key_id: AWS_SQS_SNS_KMS_MASTER_KEY_ID
|
36
|
+
mq_aws_sqs_sns_kms_master_key_id: AWS_SQS_SNS_KMS_MASTER_KEY_ID,
|
37
|
+
mq_worker_pool_min_threads: 1,
|
38
|
+
mq_worker_pool_max_threads: 2,
|
39
|
+
mq_worker_pool_max_backlog: 4,
|
40
|
+
mq_log_at_level: :info
|
36
41
|
)
|
37
42
|
adapter = Mimi::Messaging.adapter
|
38
43
|
queue_name = "test"
|
data/examples/subscriber.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
require "mimi/messaging/sqs_sns"
|
4
4
|
|
5
5
|
AWS_REGION = "eu-west-1"
|
6
|
-
AWS_SQS_ENDPOINT_URL
|
7
|
-
AWS_SNS_ENDPOINT_URL
|
6
|
+
AWS_SQS_ENDPOINT_URL = "http://localstack:4566"
|
7
|
+
AWS_SNS_ENDPOINT_URL = "http://localstack:4566"
|
8
8
|
AWS_ACCESS_KEY_ID = "foo"
|
9
9
|
AWS_SECRET_ACCESS_KEY = "bar"
|
10
10
|
|
@@ -20,6 +20,7 @@ class Processor
|
|
20
20
|
|
21
21
|
def self.call_event(event_type, message, opts)
|
22
22
|
puts "EVENT: #{event_type}, #{message}, headers: #{message.headers}"
|
23
|
+
sleep 1 # imitate work
|
23
24
|
end
|
24
25
|
end # class Processor
|
25
26
|
|
@@ -34,6 +35,9 @@ Mimi::Messaging.configure(
|
|
34
35
|
mq_aws_region: AWS_REGION,
|
35
36
|
mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL,
|
36
37
|
mq_aws_sns_endpoint: AWS_SNS_ENDPOINT_URL,
|
38
|
+
mq_worker_pool_min_threads: 1,
|
39
|
+
mq_worker_pool_max_threads: 2,
|
40
|
+
mq_worker_pool_max_backlog: 4,
|
37
41
|
mq_log_at_level: :info
|
38
42
|
)
|
39
43
|
adapter = Mimi::Messaging.adapter
|
@@ -52,4 +56,3 @@ ensure
|
|
52
56
|
puts "Stopping adapter"
|
53
57
|
adapter.stop
|
54
58
|
end
|
55
|
-
|
@@ -3,8 +3,9 @@
|
|
3
3
|
require "mimi/messaging"
|
4
4
|
require "aws-sdk-sqs"
|
5
5
|
require "aws-sdk-sns"
|
6
|
-
require "timeout"
|
7
6
|
require "securerandom"
|
7
|
+
require "concurrent"
|
8
|
+
require_relative "timeout_queue"
|
8
9
|
|
9
10
|
module Mimi
|
10
11
|
module Messaging
|
@@ -40,7 +41,7 @@ module Mimi
|
|
40
41
|
"." => "-"
|
41
42
|
}.freeze
|
42
43
|
|
43
|
-
attr_reader :options, :sqs_client, :sns_client
|
44
|
+
attr_reader :options, :sqs_client, :sns_client, :worker_pool
|
44
45
|
|
45
46
|
register_adapter_name "sqs_sns"
|
46
47
|
|
@@ -49,6 +50,11 @@ module Mimi
|
|
49
50
|
mq_default_query_timeout: 15, # seconds,
|
50
51
|
mq_reply_queue_prefix: "reply-",
|
51
52
|
|
53
|
+
# worker pool parameters
|
54
|
+
mq_worker_pool_min_threads: 1,
|
55
|
+
mq_worker_pool_max_threads: 16,
|
56
|
+
mq_worker_pool_max_backlog: 16,
|
57
|
+
|
52
58
|
# if nil, AWS SDK will guess values from environment
|
53
59
|
mq_aws_region: nil,
|
54
60
|
mq_aws_access_key_id: nil,
|
@@ -74,16 +80,19 @@ module Mimi
|
|
74
80
|
#
|
75
81
|
def initialize(options)
|
76
82
|
@options = DEFAULT_OPTIONS.merge(options).dup
|
83
|
+
@reply_consumer_mutex = Mutex.new
|
77
84
|
end
|
78
85
|
|
79
86
|
def start
|
80
87
|
@sqs_client = Aws::SQS::Client.new(sqs_client_config)
|
81
88
|
@sns_client = Aws::SNS::Client.new(sns_client_config)
|
89
|
+
start_worker_pool!
|
82
90
|
check_availability!
|
83
91
|
end
|
84
92
|
|
85
93
|
def stop
|
86
94
|
stop_all_processors
|
95
|
+
stop_worker_pool!
|
87
96
|
@sqs_client = nil
|
88
97
|
@sns_client = nil
|
89
98
|
end
|
@@ -126,7 +135,7 @@ module Mimi
|
|
126
135
|
# @param opts [Hash] additional options, e.g. :timeout
|
127
136
|
#
|
128
137
|
# @return [Hash]
|
129
|
-
# @raise [SomeError,
|
138
|
+
# @raise [SomeError,Timeout::Error]
|
130
139
|
#
|
131
140
|
def query(target, message, opts = {})
|
132
141
|
queue_name, method_name = target.split("/")
|
@@ -142,10 +151,7 @@ module Mimi
|
|
142
151
|
)
|
143
152
|
deliver_message_queue(queue_url, message)
|
144
153
|
timeout = opts[:timeout] || options[:mq_default_query_timeout]
|
145
|
-
response =
|
146
|
-
Timeout::timeout(timeout) do
|
147
|
-
response = reply_queue.pop
|
148
|
-
end
|
154
|
+
response = reply_queue.pop(true, timeout)
|
149
155
|
deserialize(response.body)
|
150
156
|
end
|
151
157
|
|
@@ -180,22 +186,14 @@ module Mimi
|
|
180
186
|
opts = opts.dup
|
181
187
|
queue_url = find_or_create_queue(queue_name)
|
182
188
|
@consumers << Consumer.new(self, queue_url) do |m|
|
183
|
-
|
184
|
-
|
185
|
-
deserialize_headers(m)
|
186
|
-
)
|
187
|
-
method_name = message.headers[:__method]
|
188
|
-
reply_to = message.headers[:__reply_queue_url]
|
189
|
-
if reply_to
|
190
|
-
response = processor.call_query(method_name, message, {})
|
191
|
-
response_message = Mimi::Messaging::Message.new(
|
192
|
-
response,
|
193
|
-
__request_id: message.headers[:__request_id]
|
194
|
-
)
|
195
|
-
deliver_query_respone(reply_to, response_message)
|
196
|
-
else
|
197
|
-
processor.call_command(method_name, message, {})
|
189
|
+
worker_pool.post do
|
190
|
+
process_request_message(processor, m)
|
198
191
|
end
|
192
|
+
rescue Concurrent::RejectedExecutionError
|
193
|
+
# the backlog is overflown, put the message back
|
194
|
+
Mimi::Messaging.log "Worker pool backlog is full, nack-ing the message " \
|
195
|
+
"(workers:#{worker_pool.length}, backlog:#{worker_pool.queue_length})"
|
196
|
+
raise Mimi::Messaging::NACK # exception raised in Consumer thread
|
199
197
|
end
|
200
198
|
end
|
201
199
|
|
@@ -212,12 +210,14 @@ module Mimi
|
|
212
210
|
queue_url = find_or_create_queue(queue_name)
|
213
211
|
subscribe_topic_queue(topic_arn, queue_url)
|
214
212
|
@consumers << Consumer.new(self, queue_url) do |m|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
213
|
+
worker_pool.post do
|
214
|
+
process_event_message(processor, m)
|
215
|
+
end
|
216
|
+
rescue Concurrent::RejectedExecutionError
|
217
|
+
# the backlog is overflown, put the message back
|
218
|
+
Mimi::Messaging.log "Worker pool backlog is full, nack-ing the message " \
|
219
|
+
"(workers:#{worker_pool.length}, backlog:#{worker_pool.queue_length})"
|
220
|
+
raise Mimi::Messaging::NACK # exception raised in Consumer thread
|
221
221
|
end
|
222
222
|
end
|
223
223
|
|
@@ -333,6 +333,30 @@ module Mimi
|
|
333
333
|
raise Mimi::Messaging::ConnectionError, "Failed to deliver message to '#{queue_url}': #{e}"
|
334
334
|
end
|
335
335
|
|
336
|
+
# Processes an incoming COMMAND or QUERY message
|
337
|
+
#
|
338
|
+
# @param processor [#call_query(),#call_command()] request processor object
|
339
|
+
# @param sqs_message
|
340
|
+
#
|
341
|
+
def process_request_message(processor, sqs_message)
|
342
|
+
message = Mimi::Messaging::Message.new(
|
343
|
+
deserialize(sqs_message.body),
|
344
|
+
deserialize_headers(sqs_message)
|
345
|
+
)
|
346
|
+
method_name = message.headers[:__method]
|
347
|
+
reply_to = message.headers[:__reply_queue_url]
|
348
|
+
if reply_to
|
349
|
+
response = processor.call_query(method_name, message, {})
|
350
|
+
response_message = Mimi::Messaging::Message.new(
|
351
|
+
response,
|
352
|
+
__request_id: message.headers[:__request_id]
|
353
|
+
)
|
354
|
+
deliver_query_response(reply_to, response_message)
|
355
|
+
else
|
356
|
+
processor.call_command(method_name, message, {})
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
336
360
|
# Delivers a message as a response to a QUERY
|
337
361
|
#
|
338
362
|
# Responses are allowed to fail. There can be a number of reasons
|
@@ -343,7 +367,7 @@ module Mimi
|
|
343
367
|
# @param queue_url [String]
|
344
368
|
# @param message [Mimi::Messaging::Message]
|
345
369
|
#
|
346
|
-
def
|
370
|
+
def deliver_query_response(queue_url, message)
|
347
371
|
deliver_message_queue(queue_url, message)
|
348
372
|
rescue Mimi::Messaging::ConnectionError => e
|
349
373
|
Mimi::Messaging.logger&.warn("Failed to deliver QRY response: #{e}")
|
@@ -402,9 +426,11 @@ module Mimi
|
|
402
426
|
# @return [ReplyConsumer]
|
403
427
|
#
|
404
428
|
def reply_consumer
|
405
|
-
@
|
406
|
-
|
407
|
-
|
429
|
+
@reply_consumer_mutex.synchronize do
|
430
|
+
@reply_consumer ||= begin
|
431
|
+
reply_queue_name = options[:mq_reply_queue_prefix] + SecureRandom.hex(8)
|
432
|
+
Mimi::Messaging::SQS_SNS::ReplyConsumer.new(self, reply_queue_name)
|
433
|
+
end
|
408
434
|
end
|
409
435
|
end
|
410
436
|
|
@@ -540,6 +566,46 @@ module Mimi
|
|
540
566
|
rescue StandardError => e
|
541
567
|
raise Mimi::Messaging::ConnectionError, "Failed to deliver message to '#{topic_arn}': #{e}"
|
542
568
|
end
|
569
|
+
|
570
|
+
# Processes an incoming EVENT message
|
571
|
+
#
|
572
|
+
# @param processor [#call_event()] event processor object
|
573
|
+
# @param sqs_message []
|
574
|
+
#
|
575
|
+
def process_event_message(processor, sqs_message)
|
576
|
+
message = Mimi::Messaging::Message.new(
|
577
|
+
deserialize(sqs_message.body),
|
578
|
+
deserialize_headers(sqs_message)
|
579
|
+
)
|
580
|
+
event_type = message.headers[:__event_type]
|
581
|
+
processor.call_event(event_type, message, {})
|
582
|
+
end
|
583
|
+
|
584
|
+
# Starts the worker pool using current configuration
|
585
|
+
#
|
586
|
+
# @return [Concurrent::ThreadPoolExecutor]
|
587
|
+
#
|
588
|
+
def start_worker_pool!
|
589
|
+
Mimi::Messaging.log "Starting worker pool, " \
|
590
|
+
"min_threads:#{options[:mq_worker_pool_min_threads]}, " \
|
591
|
+
"max_threads:#{options[:mq_worker_pool_max_threads]}, " \
|
592
|
+
"max_backlog:#{options[:mq_worker_pool_max_backlog]}"
|
593
|
+
|
594
|
+
@worker_pool = Concurrent::ThreadPoolExecutor.new(
|
595
|
+
min_threads: options[:mq_worker_pool_min_threads],
|
596
|
+
max_threads: options[:mq_worker_pool_max_threads],
|
597
|
+
max_queue: options[:mq_worker_pool_max_backlog],
|
598
|
+
fallback_policy: :abort
|
599
|
+
)
|
600
|
+
end
|
601
|
+
|
602
|
+
# Gracefully stops the worker pool, allowing all threads to finish their jobs
|
603
|
+
#
|
604
|
+
def stop_worker_pool!
|
605
|
+
Mimi::Messaging.log "Stopping worker pool"
|
606
|
+
@worker_pool.shutdown
|
607
|
+
@worker_pool.wait_for_termination
|
608
|
+
end
|
543
609
|
end # class Adapter
|
544
610
|
end # module SQS_SNS
|
545
611
|
end # module Messaging
|
@@ -7,6 +7,9 @@ module Mimi
|
|
7
7
|
# Message consumer for SQS queues
|
8
8
|
#
|
9
9
|
class Consumer
|
10
|
+
# (seconds) determines how soon the NACK-ed message becomes visible to other consumers
|
11
|
+
NACK_VISIBILITY_TIMEOUT = 1
|
12
|
+
|
10
13
|
def initialize(adapter, queue_url, &block)
|
11
14
|
@stop_requested = false
|
12
15
|
Mimi::Messaging.log "Starting consumer for: #{queue_url}"
|
@@ -47,6 +50,9 @@ module Mimi
|
|
47
50
|
Mimi::Messaging.log "Read message from: #{queue_url}"
|
48
51
|
block.call(message)
|
49
52
|
ack_message(adapter, queue_url, message)
|
53
|
+
rescue Mimi::Messaging::NACK
|
54
|
+
Mimi::Messaging.log "NACK-ing message from: #{queue_url}"
|
55
|
+
nack_message(adapter, queue_url, message)
|
50
56
|
rescue StandardError => e
|
51
57
|
Mimi::Messaging.logger&.error(
|
52
58
|
"#{self.class}: failed to read and process message from: #{queue_url}," \
|
@@ -67,11 +73,25 @@ module Mimi
|
|
67
73
|
raise Mimi::Messaging::ConnectionError, "Unexpected number of messages read"
|
68
74
|
end
|
69
75
|
|
76
|
+
# ACK-ing the message indicates successfull processing of it
|
77
|
+
# and removes the message from the queue
|
78
|
+
#
|
70
79
|
def ack_message(adapter, queue_url, msg)
|
71
80
|
adapter.sqs_client.delete_message(
|
72
81
|
queue_url: queue_url, receipt_handle: msg.receipt_handle
|
73
82
|
)
|
74
83
|
end
|
84
|
+
|
85
|
+
# NACK-ing the message indicates a failure to process the message.
|
86
|
+
# The message becomes immediately available to other consumers.
|
87
|
+
#
|
88
|
+
def nack_message(adapter, queue_url, msg)
|
89
|
+
adapter.sqs_client.change_message_visibility(
|
90
|
+
queue_url: queue_url,
|
91
|
+
receipt_handle: msg.receipt_handle,
|
92
|
+
visibility_timeout: NACK_VISIBILITY_TIMEOUT
|
93
|
+
)
|
94
|
+
end
|
75
95
|
end # class Consumer
|
76
96
|
end # module SQS_SNS
|
77
97
|
end # module Messaging
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "timeout"
|
4
|
+
|
5
|
+
module Mimi
|
6
|
+
module Messaging
|
7
|
+
module SQS_SNS
|
8
|
+
# TimeoutQueue solves the problem the native Ruby Queue class has with waiting for elements.
|
9
|
+
#
|
10
|
+
# See the excellent blog post discussing the issue:
|
11
|
+
# https://medium.com/workday-engineering/ruby-concurrency-building-a-timeout-queue-5d7c588ca80d
|
12
|
+
#
|
13
|
+
# TLDR -- using Ruby standard Timeout.timeout() around Queue#pop() is unsafe
|
14
|
+
#
|
15
|
+
#
|
16
|
+
class TimeoutQueue
|
17
|
+
def initialize
|
18
|
+
@elems = []
|
19
|
+
@mutex = Mutex.new
|
20
|
+
@cond_var = ConditionVariable.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Pushes an element into the queue
|
24
|
+
#
|
25
|
+
# @param elem [Object]
|
26
|
+
#
|
27
|
+
def <<(elem)
|
28
|
+
@mutex.synchronize do
|
29
|
+
@elems << elem
|
30
|
+
@cond_var.signal
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias push <<
|
34
|
+
|
35
|
+
# Pops an element from the queue in either non-blocking
|
36
|
+
# or a blocking (with an optional timeout) way.
|
37
|
+
#
|
38
|
+
# @param blocking [true,false] wait for a new element (true) or return immediately
|
39
|
+
# @param timeout [nil,Integer] if in blocking mode,
|
40
|
+
# wait at most given number of seconds or forever (nil)
|
41
|
+
# @raise [Timeout::Error] if a timeout in blocking mode was reached
|
42
|
+
#
|
43
|
+
def pop(blocking = true, timeout = nil)
|
44
|
+
@mutex.synchronize do
|
45
|
+
if blocking
|
46
|
+
if timeout.nil?
|
47
|
+
while @elems.empty?
|
48
|
+
@cond_var.wait(@mutex)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
timeout_time = Time.now.to_f + timeout
|
52
|
+
while @elems.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0
|
53
|
+
@cond_var.wait(@mutex, remaining_time)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
raise Timeout::Error, "queue timeout expired" if @elems.empty?
|
58
|
+
|
59
|
+
@elems.shift
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end # class TimeoutQueue
|
63
|
+
end # module SQS_SNS
|
64
|
+
end # module Messaging
|
65
|
+
end # module Mimi
|
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency "mimi-messaging", "~> 1.2"
|
37
37
|
spec.add_dependency "aws-sdk-sqs", "~> 1.22"
|
38
38
|
spec.add_dependency "aws-sdk-sns", "~> 1.19"
|
39
|
+
spec.add_dependency "concurrent-ruby"
|
39
40
|
|
40
41
|
spec.add_development_dependency "bundler", "~> 2.0"
|
41
42
|
spec.add_development_dependency "pry", "~> 0.12"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mimi-messaging-sqs_sns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Kukushkin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mimi-messaging
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.19'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: concurrent-ruby
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: bundler
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,6 +148,7 @@ files:
|
|
134
148
|
- bin/console
|
135
149
|
- bin/setup
|
136
150
|
- examples/event.rb
|
151
|
+
- examples/mass-query.rb
|
137
152
|
- examples/query.rb
|
138
153
|
- examples/responder.rb
|
139
154
|
- examples/subscriber.rb
|
@@ -142,6 +157,7 @@ files:
|
|
142
157
|
- lib/mimi/messaging/sqs_sns/consumer.rb
|
143
158
|
- lib/mimi/messaging/sqs_sns/reply_consumer.rb
|
144
159
|
- lib/mimi/messaging/sqs_sns/temporary_queue_consumer.rb
|
160
|
+
- lib/mimi/messaging/sqs_sns/timeout_queue.rb
|
145
161
|
- lib/mimi/messaging/sqs_sns/version.rb
|
146
162
|
- mimi-messaging-sqs_sns.gemspec
|
147
163
|
homepage: https://github.com/kukushkin/mimi-messaging-sqs_sns
|