mimi-messaging-sqs_sns 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|