pika_que 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/demo.rb +42 -0
- data/examples/demo_delay.rb +41 -0
- data/examples/demo_oneoff.rb +29 -0
- data/examples/demo_priority.rb +52 -0
- data/examples/demo_reporter.rb +19 -0
- data/examples/demo_retry.rb +41 -0
- data/examples/demo_worker.rb +17 -0
- data/examples/dev_worker.rb +19 -0
- data/exe/pika_que +8 -0
- data/lib/active_job/queue_adapters/pika_que_adapter.rb +42 -0
- data/lib/pika_que.rb +44 -0
- data/lib/pika_que/broker.rb +88 -0
- data/lib/pika_que/cli.rb +180 -0
- data/lib/pika_que/codecs/json.rb +22 -0
- data/lib/pika_que/codecs/noop.rb +20 -0
- data/lib/pika_que/codecs/rails.rb +22 -0
- data/lib/pika_que/configuration.rb +110 -0
- data/lib/pika_que/connection.rb +47 -0
- data/lib/pika_que/delay_worker.rb +55 -0
- data/lib/pika_que/errors.rb +5 -0
- data/lib/pika_que/handlers/default_handler.rb +31 -0
- data/lib/pika_que/handlers/delay_handler.rb +124 -0
- data/lib/pika_que/handlers/error_handler.rb +69 -0
- data/lib/pika_que/handlers/retry_handler.rb +186 -0
- data/lib/pika_que/launcher.rb +92 -0
- data/lib/pika_que/logging.rb +33 -0
- data/lib/pika_que/metrics.rb +26 -0
- data/lib/pika_que/metrics/log_metric.rb +23 -0
- data/lib/pika_que/metrics/null_metric.rb +14 -0
- data/lib/pika_que/middleware/active_record.rb +13 -0
- data/lib/pika_que/middleware/chain.rb +90 -0
- data/lib/pika_que/processor.rb +45 -0
- data/lib/pika_que/publisher.rb +23 -0
- data/lib/pika_que/rails.rb +62 -0
- data/lib/pika_que/reporters.rb +18 -0
- data/lib/pika_que/reporters/log_reporter.rb +13 -0
- data/lib/pika_que/runner.rb +24 -0
- data/lib/pika_que/subscriber.rb +80 -0
- data/lib/pika_que/util.rb +17 -0
- data/lib/pika_que/version.rb +3 -0
- data/lib/pika_que/worker.rb +99 -0
- data/pika_que.gemspec +37 -0
- metadata +181 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
module PikaQue
|
2
|
+
class Connection
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :@connection, :create_channel
|
6
|
+
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
attr_reader :connection
|
10
|
+
|
11
|
+
def initialize(opts = {})
|
12
|
+
@opts = PikaQue.config.merge(opts)
|
13
|
+
@opts[:amqp] = ENV.fetch('RABBITMQ_URL', 'amqp://guest:guest@localhost:5672')
|
14
|
+
@opts[:vhost] = AMQ::Settings.parse_amqp_url(@opts[:amqp]).fetch(:vhost, '/')
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.create(opts = {})
|
18
|
+
new(opts).tap{ |conn| conn.connect! }
|
19
|
+
end
|
20
|
+
|
21
|
+
def connect!
|
22
|
+
@connection ||= Bunny.new(@opts[:amqp], :vhost => @opts[:vhost],
|
23
|
+
:heartbeat => @opts[:heartbeat],
|
24
|
+
:properties => @opts.fetch(:properties, {}),
|
25
|
+
:logger => PikaQue::logger).tap do |conn|
|
26
|
+
conn.start
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def connected?
|
31
|
+
@connection && @connection.connected?
|
32
|
+
end
|
33
|
+
|
34
|
+
def disconnect!
|
35
|
+
@connection.close if @connection
|
36
|
+
@connection = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def ensure_connection
|
40
|
+
unless connected?
|
41
|
+
@connection = nil
|
42
|
+
connect!
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module PikaQue
|
2
|
+
class DelayWorker
|
3
|
+
|
4
|
+
attr_accessor :broker, :pool, :queue, :handler
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
@opts = PikaQue.config.merge(opts)
|
8
|
+
@broker = @opts[:broker] || PikaQue::Broker.new(nil, @opts).tap{ |b| b.start }
|
9
|
+
@pool = @opts[:worker_pool] || Concurrent::FixedThreadPool.new(@opts[:concurrency] || 1)
|
10
|
+
@delay_name = "#{@opts[:exchange]}-delay"
|
11
|
+
end
|
12
|
+
|
13
|
+
def prepare
|
14
|
+
@queue = broker.queue(@delay_name, @opts[:queue_options])
|
15
|
+
|
16
|
+
@handler = broker.handler(@opts[:handler_class], @opts[:handler_options])
|
17
|
+
# TODO use routing keys?
|
18
|
+
@handler.bind_queue(@queue, @queue.name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
@consumer = queue.subscribe(:block => false, :manual_ack => @opts[:ack]) do | delivery_info, metadata, msg |
|
23
|
+
pool.post do
|
24
|
+
work(delivery_info, metadata, msg)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def start
|
30
|
+
prepare
|
31
|
+
run
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop
|
35
|
+
@consumer.cancel if @consumer
|
36
|
+
@consumer = nil
|
37
|
+
|
38
|
+
unless @opts[:worker_pool]
|
39
|
+
@pool.shutdown
|
40
|
+
@pool.wait_for_termination 12
|
41
|
+
end
|
42
|
+
broker.cleanup
|
43
|
+
broker.stop
|
44
|
+
end
|
45
|
+
|
46
|
+
def work(delivery_info, metadata, msg)
|
47
|
+
handler.handle(:ack, broker.channel, delivery_info, metadata, msg)
|
48
|
+
end
|
49
|
+
|
50
|
+
def logger
|
51
|
+
PikaQue.logger
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module PikaQue
|
2
|
+
module Handlers
|
3
|
+
class DefaultHandler
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
# nothing to do here
|
7
|
+
end
|
8
|
+
|
9
|
+
def bind_queue(queue, routing_key)
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle(response_code, channel, delivery_info, metadata, msg, error = nil)
|
13
|
+
case response_code
|
14
|
+
when :ack
|
15
|
+
PikaQue.logger.debug "DefaultHandler acknowledge <#{msg}>"
|
16
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
17
|
+
when :requeue
|
18
|
+
PikaQue.logger.debug "DefaultHandler requeue <#{msg}>"
|
19
|
+
channel.reject(delivery_info.delivery_tag, true)
|
20
|
+
else
|
21
|
+
PikaQue.logger.debug "DefaultHandler reject <#{msg}>"
|
22
|
+
channel.reject(delivery_info.delivery_tag, false)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def close
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module PikaQue
|
2
|
+
module Handlers
|
3
|
+
class DelayHandler
|
4
|
+
|
5
|
+
# Create following exchanges with delay_name = pika-que-delay
|
6
|
+
# pika-que-delay
|
7
|
+
# pika-que-delay-requeue
|
8
|
+
# and following queues
|
9
|
+
# pika-que-delay-60
|
10
|
+
# pika-que-delay-600
|
11
|
+
# pika-que-delay-3600
|
12
|
+
# pika-que-delay-86400
|
13
|
+
#
|
14
|
+
|
15
|
+
# default delays are 1min, 10min, 1hr, 24hr
|
16
|
+
DEFAULT_DELAY_OPTS = {
|
17
|
+
:delay_periods => [60, 600, 3600, 86400],
|
18
|
+
:delay_backoff_multiplier => 1000,
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def initialize(opts = {})
|
22
|
+
@opts = PikaQue.config.merge(DEFAULT_DELAY_OPTS).merge(opts)
|
23
|
+
@connection = opts[:connection] || PikaQue.connection
|
24
|
+
@channel = @connection.create_channel
|
25
|
+
@delay_monitor = Monitor.new
|
26
|
+
@root_monitor = Monitor.new
|
27
|
+
|
28
|
+
# make sure it is in descending order
|
29
|
+
@delay_periods = @opts[:delay_periods].sort!{ |x,y| y <=> x }
|
30
|
+
@backoff_multiplier = @opts[:delay_backoff_multiplier] # This is for example/dev/test
|
31
|
+
|
32
|
+
@delay_name = "#{@opts[:exchange]}-delay"
|
33
|
+
@requeue_name = "#{@opts[:exchange]}-delay-requeue"
|
34
|
+
@root_name = @opts[:exchange]
|
35
|
+
|
36
|
+
setup_exchanges
|
37
|
+
setup_queues
|
38
|
+
end
|
39
|
+
|
40
|
+
def bind_queue(queue, routing_key)
|
41
|
+
# bind the worker queue to requeue exchange
|
42
|
+
queue.bind(@requeue_exchange, :routing_key => routing_key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle(response_code, channel, delivery_info, metadata, msg, error = nil)
|
46
|
+
delay_period = next_delay_period(metadata[:headers])
|
47
|
+
if delay_period > 0
|
48
|
+
# We will publish the message to the delay exchange
|
49
|
+
PikaQue.logger.info "DelayHandler msg=delaying, delay=#{delay_period}, headers=#{metadata[:headers]}"
|
50
|
+
|
51
|
+
publish_delay(delivery_info, msg, metadata[:headers].merge({ 'delay' => delay_period }))
|
52
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
53
|
+
else
|
54
|
+
# Publish the original message with the routing_key to the root exchange
|
55
|
+
work_queue = metadata[:headers]['work_queue']
|
56
|
+
PikaQue.logger.info "DelayHandler msg=publishing, queue=#{work_queue}, headers=#{metadata[:headers]}"
|
57
|
+
|
58
|
+
publish_work(work_queue, msg)
|
59
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def close
|
64
|
+
@channel.close unless @channel.closed?
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def setup_exchanges
|
70
|
+
PikaQue.logger.debug "DelayHandler creating exchange=#{@delay_name}"
|
71
|
+
@delay_exchange = @channel.exchange(@delay_name, :type => 'headers', :durable => exchange_durable?)
|
72
|
+
|
73
|
+
PikaQue.logger.debug "DelayHandler creating exchange=#{@requeue_name}"
|
74
|
+
@requeue_exchange = @channel.exchange(@requeue_name, :type => 'topic', :durable => exchange_durable?)
|
75
|
+
|
76
|
+
PikaQue.logger.debug "DelayHandler getting exchange=#{@root_name}"
|
77
|
+
@root_exchange = @channel.exchange(@root_name, :type => 'direct', :durable => exchange_durable?)
|
78
|
+
end
|
79
|
+
|
80
|
+
def setup_queues
|
81
|
+
@delay_periods.each do |t|
|
82
|
+
# Create the queues and bindings
|
83
|
+
PikaQue.logger.debug "DelayHandler creating queue=#{@delay_name}-#{t} x-dead-letter-exchange=#{@requeue_name}"
|
84
|
+
|
85
|
+
delay_queue = @channel.queue("#{@delay_name}-#{t}",
|
86
|
+
:durable => queue_durable?,
|
87
|
+
:arguments => {
|
88
|
+
:'x-dead-letter-exchange' => @requeue_name,
|
89
|
+
:'x-message-ttl' => t * @backoff_multiplier
|
90
|
+
})
|
91
|
+
delay_queue.bind(@delay_exchange, :arguments => { :delay => t })
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def queue_durable?
|
96
|
+
@opts.fetch(:queue_options, {}).fetch(:durable, false)
|
97
|
+
end
|
98
|
+
|
99
|
+
def exchange_durable?
|
100
|
+
@opts.fetch(:exchange_options, {}).fetch(:durable, false)
|
101
|
+
end
|
102
|
+
|
103
|
+
def publish_delay(delivery_info, msg, headers)
|
104
|
+
@delay_monitor.synchronize do
|
105
|
+
@delay_exchange.publish(msg, routing_key: delivery_info.routing_key, headers: headers)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def publish_work(routing_key, msg)
|
110
|
+
@root_monitor.synchronize do
|
111
|
+
@root_exchange.publish(msg, routing_key: routing_key)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def next_delay_period(headers)
|
116
|
+
work_at = headers['work_at']
|
117
|
+
t = (work_at - Time.now.to_f).round
|
118
|
+
# greater check is to ignore remainder of time (seconds) smaller than the last delay
|
119
|
+
@delay_periods.bsearch{ |e| t >= e && (t / e.to_f).round > 0 } || 0
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module PikaQue
|
2
|
+
module Handlers
|
3
|
+
class ErrorHandler
|
4
|
+
|
5
|
+
DEFAULT_ERROR_OPTS = {
|
6
|
+
:exchange => 'pika-que-error',
|
7
|
+
:exchange_options => { :type => :topic },
|
8
|
+
:queue => 'pika-que-error',
|
9
|
+
:routing_key => '#'
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def initialize(opts = {})
|
13
|
+
@opts = PikaQue.config.merge(DEFAULT_ERROR_OPTS).merge(opts)
|
14
|
+
@connection = @opts[:connection] || PikaQue.connection
|
15
|
+
@channel = @connection.create_channel
|
16
|
+
@exchange = @channel.exchange(@opts[:exchange], type: exchange_type, durable: exchange_durable?)
|
17
|
+
@queue = @channel.queue(@opts[:queue], durable: queue_durable?)
|
18
|
+
@queue.bind(@exchange, routing_key: @opts[:routing_key])
|
19
|
+
@monitor = Monitor.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def bind_queue(queue, routing_key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle(response_code, channel, delivery_info, metadata, msg, error = nil)
|
26
|
+
case response_code
|
27
|
+
when :ack
|
28
|
+
PikaQue.logger.debug "ErrorHandler acknowledge <#{msg}>"
|
29
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
30
|
+
when :reject
|
31
|
+
PikaQue.logger.debug "ErrorHandler reject <#{msg}>"
|
32
|
+
channel.reject(delivery_info.delivery_tag, false)
|
33
|
+
when :requeue
|
34
|
+
PikaQue.logger.debug "ErrorHandler requeue <#{msg}>"
|
35
|
+
channel.reject(delivery_info.delivery_tag, true)
|
36
|
+
else
|
37
|
+
PikaQue.logger.debug "ErrorHandler publishing <#{msg}> to [#{@queue.name}]"
|
38
|
+
publish(delivery_info, msg)
|
39
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
@channel.close unless @channel.closed?
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def queue_durable?
|
50
|
+
@opts.fetch(:queue_options, {}).fetch(:durable, false)
|
51
|
+
end
|
52
|
+
|
53
|
+
def exchange_durable?
|
54
|
+
@opts.fetch(:exchange_options, {}).fetch(:durable, false)
|
55
|
+
end
|
56
|
+
|
57
|
+
def exchange_type
|
58
|
+
@opts.fetch(:exchange_options, {}).fetch(:type, :topic)
|
59
|
+
end
|
60
|
+
|
61
|
+
def publish(delivery_info, msg)
|
62
|
+
@monitor.synchronize do
|
63
|
+
@exchange.publish(msg, routing_key: delivery_info.routing_key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module PikaQue
|
2
|
+
module Handlers
|
3
|
+
class RetryHandler
|
4
|
+
|
5
|
+
# Create following exchanges with retry_prefix = pika-que
|
6
|
+
# pika-que-retry
|
7
|
+
# pika-que-retry-requeue
|
8
|
+
# pika-que-error
|
9
|
+
# and following queues
|
10
|
+
# pika-que-retry-60 (default for const mode)
|
11
|
+
# pika-que-retry-120
|
12
|
+
# pika-que-retry-240
|
13
|
+
# pika-que-retry-480
|
14
|
+
# pika-que-retry-960
|
15
|
+
#
|
16
|
+
# retry_mode can be either :exp or :const
|
17
|
+
|
18
|
+
DEFAULT_RETRY_OPTS = {
|
19
|
+
:retry_prefix => 'pika-que',
|
20
|
+
:retry_mode => :exp,
|
21
|
+
:retry_max_times => 5,
|
22
|
+
:retry_backoff_base => 0,
|
23
|
+
:retry_backoff_multiplier => 1000,
|
24
|
+
:retry_const_backoff => 60
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
def initialize(opts = {})
|
28
|
+
@opts = PikaQue.config.merge(DEFAULT_RETRY_OPTS).merge(opts)
|
29
|
+
@connection = opts[:connection] || PikaQue.connection
|
30
|
+
@channel = @connection.create_channel
|
31
|
+
@retry_monitor = Monitor.new
|
32
|
+
@error_monitor = Monitor.new
|
33
|
+
|
34
|
+
@max_retries = @opts[:retry_max_times]
|
35
|
+
@backoff_base = @opts[:retry_backoff_base]
|
36
|
+
@backoff_multiplier = @opts[:retry_backoff_multiplier] # This is for example/dev/test
|
37
|
+
|
38
|
+
@retry_name = "#{@opts[:retry_prefix]}-retry"
|
39
|
+
@requeue_name = "#{@opts[:retry_prefix]}-retry-requeue"
|
40
|
+
@error_name = "#{@opts[:retry_prefix]}-error"
|
41
|
+
|
42
|
+
setup_exchanges
|
43
|
+
setup_queues
|
44
|
+
end
|
45
|
+
|
46
|
+
def bind_queue(queue, routing_key)
|
47
|
+
# bind the worker queue to requeue exchange
|
48
|
+
queue.bind(@requeue_exchange, :routing_key => routing_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle(response_code, channel, delivery_info, metadata, msg, error = nil)
|
52
|
+
case response_code
|
53
|
+
when :ack
|
54
|
+
PikaQue.logger.debug "RetryHandler acknowledge <#{msg}>"
|
55
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
56
|
+
when :reject
|
57
|
+
PikaQue.logger.debug "RetryHandler reject retry <#{msg}>"
|
58
|
+
handle_retry(channel, delivery_info, metadata, msg, :reject)
|
59
|
+
when :requeue
|
60
|
+
PikaQue.logger.debug "RetryHandler requeue <#{msg}>"
|
61
|
+
channel.reject(delivery_info.delivery_tag, true)
|
62
|
+
else
|
63
|
+
PikaQue.logger.debug "RetryHandler error retry <#{msg}>"
|
64
|
+
handle_retry(channel, delivery_info, metadata, msg, error)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
@channel.close unless @channel.closed?
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
#####################################################
|
74
|
+
# formula
|
75
|
+
# base X = 0, 30, 60, 120, 180, etc defaults to 0
|
76
|
+
# (X + 15) * 2 ** (count + 1)
|
77
|
+
def self.backoff_periods(max_retries, backoff_base)
|
78
|
+
(1..max_retries).map{ |c| next_ttl(c, backoff_base) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.next_ttl(count, backoff_base)
|
82
|
+
(backoff_base + 15) * 2 ** (count + 1)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def setup_exchanges
|
88
|
+
PikaQue.logger.debug "RetryHandler creating exchange=#{@retry_name}"
|
89
|
+
@retry_exchange = @channel.exchange(@retry_name, :type => 'headers', :durable => exchange_durable?)
|
90
|
+
@error_exchange, @requeue_exchange = [@error_name, @requeue_name].map do |name|
|
91
|
+
PikaQue.logger.debug "RetryHandler creating exchange=#{name}"
|
92
|
+
@channel.exchange(name, :type => 'topic', :durable => exchange_durable?)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def setup_queues
|
97
|
+
if @opts[:retry_mode] == :const
|
98
|
+
bo = @opts[:retry_const_backoff]
|
99
|
+
PikaQue.logger.debug "RetryHandler creating queue=#{@retry_name}-#{bo} x-dead-letter-exchange=#{@requeue_name}"
|
100
|
+
backoff_queue = @channel.queue("#{@retry_name}-#{bo}",
|
101
|
+
:durable => queue_durable?,
|
102
|
+
:arguments => {
|
103
|
+
:'x-dead-letter-exchange' => @requeue_name,
|
104
|
+
:'x-message-ttl' => bo * @backoff_multiplier
|
105
|
+
})
|
106
|
+
backoff_queue.bind(@retry_exchange, :arguments => { :backoff => bo })
|
107
|
+
else
|
108
|
+
backoffs = Expbackoff.backoff_periods(@max_retries, @backoff_base)
|
109
|
+
backoffs.each do |bo|
|
110
|
+
PikaQue.logger.debug "RetryHandler creating queue=#{@retry_name}-#{bo} x-dead-letter-exchange=#{@requeue_name}"
|
111
|
+
backoff_queue = @channel.queue("#{@retry_name}-#{bo}",
|
112
|
+
:durable => queue_durable?,
|
113
|
+
:arguments => {
|
114
|
+
:'x-dead-letter-exchange' => @requeue_name,
|
115
|
+
:'x-message-ttl' => bo * @backoff_multiplier
|
116
|
+
})
|
117
|
+
backoff_queue.bind(@retry_exchange, :arguments => { :backoff => bo })
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
PikaQue.logger.debug "RetryHandler creating queue=#{@error_name}"
|
122
|
+
@error_queue = @channel.queue(@error_name, :durable => queue_durable?)
|
123
|
+
@error_queue.bind(@error_exchange, :routing_key => '#')
|
124
|
+
end
|
125
|
+
|
126
|
+
def queue_durable?
|
127
|
+
@opts.fetch(:queue_options, {}).fetch(:durable, false)
|
128
|
+
end
|
129
|
+
|
130
|
+
def exchange_durable?
|
131
|
+
@opts.fetch(:exchange_options, {}).fetch(:durable, false)
|
132
|
+
end
|
133
|
+
|
134
|
+
def handle_retry(channel, delivery_info, metadata, msg, reason)
|
135
|
+
# +1 for the current attempt
|
136
|
+
num_attempts = failure_count(metadata[:headers]) + 1
|
137
|
+
if num_attempts <= @max_retries
|
138
|
+
# Publish message to the x-dead-letter-exchange (ie. retry exchange)
|
139
|
+
PikaQue.logger.info "RetryHandler msg=retrying, count=#{num_attempts}, headers=#{metadata[:headers] || {}}"
|
140
|
+
|
141
|
+
if @opts[:retry_mode] == :exp
|
142
|
+
backoff_ttl = Expbackoff.next_ttl(num_attempts, @backoff_base)
|
143
|
+
else
|
144
|
+
backoff_ttl = @opts[:retry_const_backoff]
|
145
|
+
end
|
146
|
+
|
147
|
+
publish_retry(delivery_info, msg, { backoff: backoff_ttl, count: num_attempts })
|
148
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
149
|
+
else
|
150
|
+
PikaQue.logger.info "RetryHandler msg=failing, retry_count=#{num_attempts}, headers=#{metadata[:headers]}, reason=#{reason}"
|
151
|
+
|
152
|
+
publish_error(delivery_info, msg)
|
153
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Uses the header to determine the number of failures this job has
|
158
|
+
# seen in the past. This does not count the current failure. So for
|
159
|
+
# instance, the first time the job fails, this will return 0, the second
|
160
|
+
# time, 1, etc.
|
161
|
+
# @param headers [Hash] Hash of headers that Rabbit delivers as part of
|
162
|
+
# the message
|
163
|
+
# @return [Integer] Count of number of failures.
|
164
|
+
def failure_count(headers)
|
165
|
+
if headers.nil? || headers['count'].nil?
|
166
|
+
0
|
167
|
+
else
|
168
|
+
headers['count']
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def publish_retry(delivery_info, msg, headers)
|
173
|
+
@retry_monitor.synchronize do
|
174
|
+
@retry_exchange.publish(msg, routing_key: delivery_info.routing_key, headers: headers)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def publish_error(delivery_info, msg)
|
179
|
+
@error_monitor.synchronize do
|
180
|
+
@error_exchange.publish(msg, routing_key: delivery_info.routing_key)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|