pika_que 0.1.6 → 0.3.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 +5 -5
- data/.github/workflows/ruby.yml +38 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -2
- data/README.md +2 -0
- data/examples/demo.rb +2 -2
- data/examples/demo_conpriority.rb +3 -2
- data/examples/demo_delay.rb +3 -2
- data/examples/demo_dlx_retry.rb +43 -0
- data/examples/demo_middleware.rb +19 -0
- data/examples/demo_retry.rb +2 -0
- data/lib/active_job/queue_adapters/pika_que_adapter.rb +5 -16
- data/lib/pika_que/cli.rb +24 -11
- data/lib/pika_que/configuration.rb +7 -11
- data/lib/pika_que/handlers/dlx_retry_handler.rb +150 -0
- data/lib/pika_que/handlers/error_handler.rb +11 -12
- data/lib/pika_que/handlers/retry_handler.rb +2 -2
- data/lib/pika_que/handlers.rb +1 -0
- data/lib/pika_que/middleware/request_store.rb +14 -0
- data/lib/pika_que/rails.rb +23 -21
- data/lib/pika_que/rails_worker.rb +15 -0
- data/lib/pika_que/runner.rb +34 -6
- data/lib/pika_que/util.rb +32 -0
- data/lib/pika_que/version.rb +1 -1
- data/lib/pika_que.rb +4 -2
- data/pika_que.gemspec +6 -5
- metadata +34 -15
- data/examples/demo_reporter.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9b776bf89fdc3f017c2d6f7b02a0c7cbf78e260b6d30917db25764b152f485c0
|
4
|
+
data.tar.gz: f7319738ad4c7ea1f0d205ebda1a7bf3b639f6ca1d85e77a5e73afa0d4fd909d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c37d9efa4413e0785483e05f082c193caa40cb1d43459772621e1ac8ccebd1572853f97ff259e8aca8133c45dbad438c9a184a5bd441f56f0711a011f39d897f
|
7
|
+
data.tar.gz: 53a818627f9cbfd6bbb5eec5b148d1a2adb82b7ac48a7487e49f03ecfccb9cc73c9debc4ea0f4edc631621c61358a58f66c8e93a2fb16eadb0f054445c30b576
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ "master" ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ "master" ]
|
15
|
+
|
16
|
+
permissions:
|
17
|
+
contents: read
|
18
|
+
|
19
|
+
jobs:
|
20
|
+
test:
|
21
|
+
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
strategy:
|
24
|
+
matrix:
|
25
|
+
ruby-version: ['2.6', '2.7', '3.0']
|
26
|
+
|
27
|
+
steps:
|
28
|
+
- uses: actions/checkout@v4
|
29
|
+
- name: Set up Ruby
|
30
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
31
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
32
|
+
# uses: ruby/setup-ruby@v1
|
33
|
+
uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
|
34
|
+
with:
|
35
|
+
ruby-version: ${{ matrix.ruby-version }}
|
36
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
37
|
+
- name: Run tests
|
38
|
+
run: bundle exec rake
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.5
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# PikaQue
|
2
2
|
|
3
|
+

|
4
|
+
|
3
5
|
A RabbitMQ background processing framework for Ruby with built-in support for Rails integration.
|
4
6
|
|
5
7
|
PikaQue is inspired by Sneakers, Hutch, and Sidekiq. It is intended to implement more support for Rails a la Sidekiq.
|
data/examples/demo.rb
CHANGED
@@ -6,12 +6,12 @@ require 'pika_que/publisher'
|
|
6
6
|
|
7
7
|
require 'dev_worker'
|
8
8
|
require 'demo_worker'
|
9
|
-
require '
|
9
|
+
require 'demo_middleware'
|
10
10
|
|
11
11
|
PikaQue.logger.level = ::Logger::DEBUG
|
12
12
|
|
13
13
|
PikaQue.middleware do |chain|
|
14
|
-
chain.add
|
14
|
+
chain.add DemoMiddleware
|
15
15
|
end
|
16
16
|
|
17
17
|
workers = [DemoWorker,DevWorker]
|
@@ -33,10 +33,11 @@ class LowPriorityWorker
|
|
33
33
|
|
34
34
|
end
|
35
35
|
|
36
|
-
PikaQue.config
|
37
|
-
PikaQue.config
|
36
|
+
PikaQue.config[:processors] << { workers: [LowPriorityWorker], concurrency: 10 }
|
37
|
+
PikaQue.config[:processors] << { workers: [HighPriorityWorker], concurrency: 10 }
|
38
38
|
|
39
39
|
runner = PikaQue::Runner.new
|
40
|
+
runner.setup_processors
|
40
41
|
|
41
42
|
begin
|
42
43
|
runner.run
|
data/examples/demo_delay.rb
CHANGED
@@ -16,10 +16,11 @@ end
|
|
16
16
|
|
17
17
|
PikaQue.logger.level = ::Logger::DEBUG
|
18
18
|
|
19
|
-
PikaQue.config
|
20
|
-
PikaQue.config
|
19
|
+
PikaQue.config[:delay] = true
|
20
|
+
PikaQue.config[:processors] << { workers: [DemoWorker] }
|
21
21
|
|
22
22
|
runner = PikaQue::Runner.new
|
23
|
+
runner.setup_processors
|
23
24
|
|
24
25
|
begin
|
25
26
|
runner.run
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# > bundle exec ruby examples/demo_dlx_retry.rb
|
2
|
+
# Retry using x-dead-letter-exchange
|
3
|
+
# Constant backoff only
|
4
|
+
require 'pika_que'
|
5
|
+
require 'pika_que/worker'
|
6
|
+
require 'pika_que/handlers/dlx_retry_handler'
|
7
|
+
|
8
|
+
class DlxWorker
|
9
|
+
include PikaQue::Worker
|
10
|
+
from_queue "pika-que-dlx", :arguments => { :'x-dead-letter-exchange' => 'pika-que-retry-60' }
|
11
|
+
handle_with PikaQue::Handlers::DLXRetryHandler, retry_max_times: 3, retry_dlx: 'pika-que-retry-60'
|
12
|
+
|
13
|
+
def perform(msg)
|
14
|
+
logger.info msg["msg"]
|
15
|
+
raise "BOOM!"
|
16
|
+
ack!
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
PikaQue.logger.level = ::Logger::DEBUG
|
22
|
+
|
23
|
+
workers = [DlxWorker]
|
24
|
+
|
25
|
+
begin
|
26
|
+
pro = PikaQue::Processor.new(workers: workers)
|
27
|
+
pro.start
|
28
|
+
rescue => e
|
29
|
+
puts e
|
30
|
+
puts e.backtrace.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
sleep 3
|
34
|
+
|
35
|
+
DlxWorker.enqueue({ msg: "retry message" })
|
36
|
+
|
37
|
+
sleep 200
|
38
|
+
|
39
|
+
pro.stop
|
40
|
+
|
41
|
+
puts "bye"
|
42
|
+
|
43
|
+
exit 1
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class DemoMiddleware
|
2
|
+
|
3
|
+
def initialize(opts = {})
|
4
|
+
STDOUT.sync = true
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(worker, delivery_info, metadata, msg)
|
8
|
+
puts "entering middleware DemoMiddleware for msg: #{msg}"
|
9
|
+
begin
|
10
|
+
yield
|
11
|
+
rescue => e
|
12
|
+
puts "error caught in middleware DemoMiddleware for msg: #{msg}, error: #{e.message}"
|
13
|
+
raise e
|
14
|
+
ensure
|
15
|
+
puts "leaving middleware DemoMiddleware for msg: #{msg}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/examples/demo_retry.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require 'pika_que'
|
2
|
-
require 'pika_que/codecs/rails'
|
1
|
+
require 'pika_que/rails_worker'
|
3
2
|
require 'thread'
|
4
3
|
|
5
4
|
module ActiveJob
|
@@ -20,21 +19,16 @@ module ActiveJob
|
|
20
19
|
class << self
|
21
20
|
def enqueue(job) #:nodoc:
|
22
21
|
@monitor.synchronize do
|
23
|
-
|
22
|
+
PikaQue::RailsWorker.enqueue job.serialize, to_queue: job.queue_name
|
24
23
|
end
|
25
24
|
end
|
26
25
|
|
27
26
|
def enqueue_at(job, timestamp) #:nodoc:
|
28
27
|
@monitor.synchronize do
|
29
|
-
|
28
|
+
PikaQue::RailsWorker.enqueue_at job.serialize, timestamp, routing_key: job.queue_name
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
33
|
-
|
34
|
-
class JobWrapper #:nodoc:
|
35
|
-
extend PikaQue::Worker::ClassMethods
|
36
|
-
config codec: PikaQue::Codecs::RAILS
|
37
|
-
end
|
38
32
|
end
|
39
33
|
|
40
34
|
class PikaQueRails5
|
@@ -44,20 +38,15 @@ module ActiveJob
|
|
44
38
|
|
45
39
|
def enqueue(job) #:nodoc:
|
46
40
|
@monitor.synchronize do
|
47
|
-
|
41
|
+
PikaQue::RailsWorker.enqueue job.serialize, to_queue: job.queue_name, priority: job.priority
|
48
42
|
end
|
49
43
|
end
|
50
44
|
|
51
45
|
def enqueue_at(job, timestamp) #:nodoc:
|
52
46
|
@monitor.synchronize do
|
53
|
-
|
47
|
+
PikaQue::RailsWorker.enqueue_at job.serialize, timestamp, routing_key: job.queue_name
|
54
48
|
end
|
55
49
|
end
|
56
|
-
|
57
|
-
class JobWrapper #:nodoc:
|
58
|
-
extend PikaQue::Worker::ClassMethods
|
59
|
-
config codec: PikaQue::Codecs::RAILS
|
60
|
-
end
|
61
50
|
end
|
62
51
|
|
63
52
|
PikaQueAdapter = (::Rails::VERSION::MAJOR < 5) ? PikaQueRails4 : PikaQueRails5
|
data/lib/pika_que/cli.rb
CHANGED
@@ -11,7 +11,7 @@ module PikaQue
|
|
11
11
|
|
12
12
|
def parse(args = ARGV)
|
13
13
|
opts = parse_options(args)
|
14
|
-
|
14
|
+
init_config(opts)
|
15
15
|
init_logger
|
16
16
|
daemonize
|
17
17
|
write_pid
|
@@ -20,10 +20,9 @@ module PikaQue
|
|
20
20
|
def run
|
21
21
|
|
22
22
|
load_app
|
23
|
+
prepare_server
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
runner = Runner.new
|
25
|
+
runner = Runner.new.tap{ |r| r.setup_processors }
|
27
26
|
|
28
27
|
begin
|
29
28
|
|
@@ -43,6 +42,13 @@ module PikaQue
|
|
43
42
|
PikaQue.config
|
44
43
|
end
|
45
44
|
|
45
|
+
def init_config(opts)
|
46
|
+
if opts[:config]
|
47
|
+
config.load(File.expand_path(opts[:config]))
|
48
|
+
end
|
49
|
+
config.merge!(opts)
|
50
|
+
end
|
51
|
+
|
46
52
|
def init_logger
|
47
53
|
PikaQue::Logging.init_logger(config[:logfile]) if config[:logfile]
|
48
54
|
PikaQue.logger.level = ::Logger::WARN if config[:quiet]
|
@@ -101,17 +107,16 @@ module PikaQue
|
|
101
107
|
else
|
102
108
|
require(File.expand_path(config[:require])) || raise(ArgumentError, 'require returned false')
|
103
109
|
end
|
110
|
+
end
|
104
111
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
config.delete(:delay_options)
|
112
|
+
def prepare_server
|
113
|
+
PikaQue.middleware do |chain|
|
114
|
+
config[:middlewares].each{ |m| chain.add PikaQue::Util.constantize(m) } if config[:middlewares]
|
109
115
|
end
|
110
116
|
|
111
|
-
|
112
|
-
config.
|
117
|
+
PikaQue.reporters do |rptrs|
|
118
|
+
config[:reporters].each{ |r| rptrs << PikaQue::Util.constantize(r).new }
|
113
119
|
end
|
114
|
-
|
115
120
|
end
|
116
121
|
|
117
122
|
def parse_options(args)
|
@@ -148,6 +153,14 @@ module PikaQue
|
|
148
153
|
opts[:workers] = arg.split(",")
|
149
154
|
end
|
150
155
|
|
156
|
+
o.on '--no-delay', "turn off delay processor" do |arg|
|
157
|
+
opts[:delay] = arg
|
158
|
+
end
|
159
|
+
|
160
|
+
o.on '-C', '--config PATH', "path to config yml file" do |arg|
|
161
|
+
opts[:config] = arg
|
162
|
+
end
|
163
|
+
|
151
164
|
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
152
165
|
opts[:logfile] = arg
|
153
166
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'forwardable'
|
2
|
+
require 'yaml'
|
2
3
|
|
3
4
|
require 'pika_que/codecs/json'
|
4
5
|
require 'pika_que/codecs/noop'
|
@@ -81,6 +82,12 @@ module PikaQue
|
|
81
82
|
@config[:vhost] = AMQ::Settings.parse_amqp_url(@config[:amqp]).fetch(:vhost, '/')
|
82
83
|
end
|
83
84
|
|
85
|
+
def load(filename)
|
86
|
+
loaded = YAML.load_file(filename)
|
87
|
+
converted = JSON.parse(JSON.dump(loaded), symbolize_names: true)
|
88
|
+
merge! converted
|
89
|
+
end
|
90
|
+
|
84
91
|
def merge!(other = {})
|
85
92
|
@config = deep_merge(@config, other)
|
86
93
|
end
|
@@ -97,16 +104,5 @@ module PikaQue
|
|
97
104
|
first.merge(second, &merger)
|
98
105
|
end
|
99
106
|
|
100
|
-
def processor(opts = {})
|
101
|
-
{
|
102
|
-
:processor => PikaQue::Processor,
|
103
|
-
:workers => []
|
104
|
-
}.merge(opts)
|
105
|
-
end
|
106
|
-
|
107
|
-
def add_processor(opts = {})
|
108
|
-
@config[:processors] << processor(opts)
|
109
|
-
end
|
110
|
-
|
111
107
|
end
|
112
108
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module PikaQue
|
2
|
+
module Handlers
|
3
|
+
class DLXRetryHandler
|
4
|
+
|
5
|
+
# Create following exchanges with retry_prefix = pika-que and default backoff
|
6
|
+
# pika-que-retry-60
|
7
|
+
# pika-que-retry-requeue
|
8
|
+
# pika-que-error
|
9
|
+
# and following queue
|
10
|
+
# pika-que-retry-60 (with default backoff)
|
11
|
+
#
|
12
|
+
|
13
|
+
DEFAULT_RETRY_OPTS = {
|
14
|
+
:retry_prefix => 'pika-que',
|
15
|
+
:retry_max_times => 5,
|
16
|
+
:retry_backoff => 60,
|
17
|
+
:retry_backoff_multiplier => 1000,
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
def initialize(opts = {})
|
21
|
+
@opts = PikaQue.config.merge(DEFAULT_RETRY_OPTS).merge(opts)
|
22
|
+
@connection = opts[:connection] || PikaQue.connection
|
23
|
+
@channel = @connection.create_channel
|
24
|
+
@error_monitor = Monitor.new
|
25
|
+
|
26
|
+
@max_retries = @opts[:retry_max_times]
|
27
|
+
@backoff_multiplier = @opts[:retry_backoff_multiplier] # This is for example/dev/test
|
28
|
+
|
29
|
+
@retry_ex_name = @opts[:retry_dlx] || "#{@opts[:retry_prefix]}-retry-#{@opts[:retry_backoff]}"
|
30
|
+
@retry_name = "#{@opts[:retry_prefix]}-retry"
|
31
|
+
@requeue_name = "#{@opts[:retry_prefix]}-retry-requeue"
|
32
|
+
@error_name = "#{@opts[:retry_prefix]}-error"
|
33
|
+
|
34
|
+
@queue_name_lookup = {}
|
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_name_lookup[routing_key] = queue.name
|
43
|
+
queue.bind(@requeue_exchange, :routing_key => routing_key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def handle(response_code, channel, delivery_info, metadata, msg, error = nil)
|
47
|
+
case response_code
|
48
|
+
when :ack
|
49
|
+
PikaQue.logger.debug "DLXRetryHandler acknowledge <#{msg}>"
|
50
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
51
|
+
when :reject
|
52
|
+
PikaQue.logger.debug "DLXRetryHandler reject retry <#{msg}>"
|
53
|
+
handle_retry(channel, delivery_info, metadata, msg, :reject)
|
54
|
+
when :requeue
|
55
|
+
PikaQue.logger.debug "DLXRetryHandler requeue <#{msg}>"
|
56
|
+
channel.reject(delivery_info.delivery_tag, true)
|
57
|
+
else
|
58
|
+
PikaQue.logger.debug "DLXRetryHandler error retry <#{msg}>"
|
59
|
+
handle_retry(channel, delivery_info, metadata, msg, error)
|
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
|
+
@retry_exchange, @error_exchange, @requeue_exchange = [@retry_ex_name, @error_name, @requeue_name].map do |name|
|
71
|
+
PikaQue.logger.debug "DLXRetryHandler creating exchange=#{name}"
|
72
|
+
@channel.exchange(name, :type => 'topic', :durable => exchange_durable?)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup_queues
|
77
|
+
bo = @opts[:retry_backoff]
|
78
|
+
|
79
|
+
PikaQue.logger.debug "DLXRetryHandler creating queue=#{@retry_name}-#{bo} x-dead-letter-exchange=#{@requeue_name}"
|
80
|
+
backoff_queue = @channel.queue("#{@retry_name}-#{bo}",
|
81
|
+
:durable => queue_durable?,
|
82
|
+
:arguments => {
|
83
|
+
:'x-dead-letter-exchange' => @requeue_name,
|
84
|
+
:'x-message-ttl' => bo * @backoff_multiplier
|
85
|
+
})
|
86
|
+
backoff_queue.bind(@retry_exchange, :routing_key => '#')
|
87
|
+
|
88
|
+
PikaQue.logger.debug "DLXRetryHandler creating queue=#{@error_name}"
|
89
|
+
@error_queue = @channel.queue(@error_name, :durable => queue_durable?)
|
90
|
+
@error_queue.bind(@error_exchange, :routing_key => '#')
|
91
|
+
end
|
92
|
+
|
93
|
+
def queue_durable?
|
94
|
+
@opts.fetch(:queue_options, {}).fetch(:durable, false)
|
95
|
+
end
|
96
|
+
|
97
|
+
def exchange_durable?
|
98
|
+
@opts.fetch(:exchange_options, {}).fetch(:durable, false)
|
99
|
+
end
|
100
|
+
|
101
|
+
def handle_retry(channel, delivery_info, metadata, msg, reason)
|
102
|
+
# +1 for the current attempt
|
103
|
+
num_attempts = failure_count(metadata[:headers], delivery_info) + 1
|
104
|
+
if num_attempts <= @max_retries
|
105
|
+
# Publish message to the x-dead-letter-exchange (ie. retry exchange)
|
106
|
+
PikaQue.logger.info "DLXRetryHandler msg=retrying, count=#{num_attempts}, headers=#{metadata[:headers] || {}}"
|
107
|
+
|
108
|
+
channel.reject(delivery_info.delivery_tag, false)
|
109
|
+
else
|
110
|
+
PikaQue.logger.info "DLXRetryHandler msg=failing, retried_count=#{num_attempts - 1}, headers=#{metadata[:headers]}, reason=#{reason}"
|
111
|
+
|
112
|
+
publish_error(delivery_info, msg)
|
113
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Uses the x-death header to determine the number of failures this job has
|
118
|
+
# seen in the past. This does not count the current failure. So for
|
119
|
+
# instance, the first time the job fails, this will return 0, the second
|
120
|
+
# time, 1, etc.
|
121
|
+
# @param headers [Hash] Hash of headers that Rabbit delivers as part of
|
122
|
+
# the message
|
123
|
+
# @return [Integer] Count of number of failures.
|
124
|
+
def failure_count(headers, delivery_info)
|
125
|
+
if headers.nil? || headers['x-death'].nil?
|
126
|
+
0
|
127
|
+
else
|
128
|
+
queue_name = headers['x-first-death-queue'] || @queue_name_lookup[delivery_info.routing_key]
|
129
|
+
x_death_array = headers['x-death'].select do |x_death|
|
130
|
+
x_death['queue'] == queue_name
|
131
|
+
end
|
132
|
+
if x_death_array.count > 0 && x_death_array.first['count']
|
133
|
+
# Newer versions of RabbitMQ return headers with a count key
|
134
|
+
x_death_array.inject(0) {|sum, x_death| sum + x_death['count']}
|
135
|
+
else
|
136
|
+
# Older versions return a separate x-death header for each failure
|
137
|
+
x_death_array.count
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def publish_error(delivery_info, msg)
|
143
|
+
@error_monitor.synchronize do
|
144
|
+
@error_exchange.publish(msg, routing_key: delivery_info.routing_key)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -3,19 +3,22 @@ module PikaQue
|
|
3
3
|
class ErrorHandler
|
4
4
|
|
5
5
|
DEFAULT_ERROR_OPTS = {
|
6
|
-
:
|
7
|
-
:exchange_options => { :type => :topic },
|
8
|
-
:queue => 'pika-que-error',
|
9
|
-
:routing_key => '#'
|
6
|
+
:error_prefix => 'pika-que'
|
10
7
|
}.freeze
|
11
8
|
|
12
9
|
def initialize(opts = {})
|
13
10
|
@opts = PikaQue.config.merge(DEFAULT_ERROR_OPTS).merge(opts)
|
14
11
|
@connection = @opts[:connection] || PikaQue.connection
|
15
12
|
@channel = @connection.create_channel
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
error_ex_name = error_q_name = "#{@opts[:error_prefix]}-error"
|
14
|
+
if @opts[:queue]
|
15
|
+
# handle deprecated options
|
16
|
+
error_ex_name = @opts[:exchange]
|
17
|
+
error_q_name = @opts[:queue]
|
18
|
+
end
|
19
|
+
@exchange = @channel.exchange(error_ex_name, type: :topic, durable: exchange_durable?)
|
20
|
+
@queue = @channel.queue(error_q_name, durable: queue_durable?)
|
21
|
+
@queue.bind(@exchange, routing_key: '#')
|
19
22
|
@monitor = Monitor.new
|
20
23
|
end
|
21
24
|
|
@@ -36,7 +39,7 @@ module PikaQue
|
|
36
39
|
else
|
37
40
|
PikaQue.logger.debug "ErrorHandler publishing <#{msg}> to [#{@queue.name}]"
|
38
41
|
publish(delivery_info, msg)
|
39
|
-
channel.
|
42
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
@@ -54,10 +57,6 @@ module PikaQue
|
|
54
57
|
@opts.fetch(:exchange_options, {}).fetch(:durable, false)
|
55
58
|
end
|
56
59
|
|
57
|
-
def exchange_type
|
58
|
-
@opts.fetch(:exchange_options, {}).fetch(:type, :topic)
|
59
|
-
end
|
60
|
-
|
61
60
|
def publish(delivery_info, msg)
|
62
61
|
@monitor.synchronize do
|
63
62
|
@exchange.publish(msg, routing_key: delivery_info.routing_key)
|
@@ -138,12 +138,12 @@ module PikaQue
|
|
138
138
|
end
|
139
139
|
|
140
140
|
publish_retry(delivery_info, msg, { backoff: backoff_ttl, count: num_attempts })
|
141
|
-
channel.
|
141
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
142
142
|
else
|
143
143
|
PikaQue.logger.info "RetryHandler msg=failing, retried_count=#{num_attempts - 1}, headers=#{metadata[:headers]}, reason=#{reason}"
|
144
144
|
|
145
145
|
publish_error(delivery_info, msg)
|
146
|
-
channel.
|
146
|
+
channel.acknowledge(delivery_info.delivery_tag, false)
|
147
147
|
end
|
148
148
|
end
|
149
149
|
|
data/lib/pika_que/handlers.rb
CHANGED
@@ -4,6 +4,7 @@ module PikaQue
|
|
4
4
|
autoload :DefaultHandler, 'pika_que/handlers/default_handler'
|
5
5
|
autoload :ErrorHandler, 'pika_que/handlers/error_handler'
|
6
6
|
autoload :RetryHandler, 'pika_que/handlers/retry_handler'
|
7
|
+
autoload :DLXRetryHandler, 'pika_que/handlers/dlx_retry_handler'
|
7
8
|
|
8
9
|
end
|
9
10
|
end
|
data/lib/pika_que/rails.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require 'pika_que/
|
2
|
-
require 'pika_que/
|
1
|
+
require 'pika_que/rails_worker'
|
2
|
+
require 'pika_que/util'
|
3
3
|
|
4
4
|
module PikaQue
|
5
5
|
class Rails < ::Rails::Engine
|
@@ -20,10 +20,10 @@ module PikaQue
|
|
20
20
|
config.after_initialize do
|
21
21
|
config_file = ::Rails.root.join('config').join('pika_que.yml')
|
22
22
|
if File.exist? config_file
|
23
|
-
PIKA_QUE_CONFIG = YAML.load_file(config_file)
|
23
|
+
PIKA_QUE_CONFIG = YAML.load_file(config_file).deep_symbolize_keys
|
24
24
|
else
|
25
25
|
mailer_queue = (::Rails::VERSION::MAJOR < 5) ? ActionMailer::DeliveryJob.queue_name : ActionMailer::Base.deliver_later_queue_name
|
26
|
-
PIKA_QUE_CONFIG = {
|
26
|
+
PIKA_QUE_CONFIG = { processors: [{ workers: [{ queue: ActiveJob::Base.queue_name }, { queue: mailer_queue.to_s }] }] }
|
27
27
|
end
|
28
28
|
|
29
29
|
workers_dir = ::Rails.root.join('app').join('workers')
|
@@ -33,28 +33,30 @@ module PikaQue
|
|
33
33
|
worker_files = []
|
34
34
|
end
|
35
35
|
|
36
|
-
PIKA_QUE_CONFIG[
|
36
|
+
PIKA_QUE_CONFIG[:processors].each do |processor|
|
37
37
|
workers = []
|
38
|
-
processor[
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
ack!
|
38
|
+
processor[:workers].each do |worker|
|
39
|
+
if worker.is_a? Hash
|
40
|
+
if worker[:worker]
|
41
|
+
worker_name = worker[:worker]
|
42
|
+
else
|
43
|
+
queue_name = worker[:queue_name] || worker[:queue]
|
44
|
+
queue_opts = worker[:queue_opts] || {}
|
45
|
+
worker_name = "#{queue_name.underscore.classify}Worker"
|
46
|
+
unless worker_files.detect{ |w| w =~ /#{worker_name.underscore}/ }
|
47
|
+
PikaQue::Util.register_worker_class(worker_name, PikaQue::RailsWorker, queue_name, queue_opts)
|
49
48
|
end
|
50
49
|
end
|
51
|
-
|
50
|
+
else
|
51
|
+
worker_name = worker
|
52
|
+
end
|
52
53
|
workers << worker_name
|
53
54
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
processor[:workers] = workers
|
56
|
+
unless PikaQue.config[:workers] || PikaQue.config[:config]
|
57
|
+
PikaQue.logger.info "Adding rails processor: #{processor}"
|
58
|
+
PikaQue.config[:processors] << processor
|
59
|
+
end
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
data/lib/pika_que/runner.rb
CHANGED
@@ -2,23 +2,51 @@ module PikaQue
|
|
2
2
|
class Runner
|
3
3
|
|
4
4
|
def run
|
5
|
-
run_config = {}
|
6
|
-
|
7
5
|
# TODO anything to add to run_config?
|
6
|
+
run_config = {}
|
8
7
|
|
9
|
-
@
|
10
|
-
|
8
|
+
@processes = []
|
9
|
+
processors.each do |processor_hash|
|
11
10
|
_processor = PikaQue::Util.constantize(processor_hash[:processor]).new(processor_hash.merge(run_config))
|
12
11
|
_processor.start
|
13
|
-
@
|
12
|
+
@processes << _processor
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
16
|
# halt? pause?
|
18
17
|
def stop
|
19
|
-
@
|
18
|
+
@processes.each(&:stop)
|
20
19
|
PikaQue.connection.disconnect!
|
21
20
|
end
|
22
21
|
|
22
|
+
def setup_processors
|
23
|
+
add_processor(config[:delay_options]) if config[:delay]
|
24
|
+
if config[:workers]
|
25
|
+
add_processor({ workers: config[:workers] })
|
26
|
+
else
|
27
|
+
config[:processors].each{ |p| add_processor(p) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def processor(opts = {})
|
32
|
+
{
|
33
|
+
:processor => PikaQue::Processor,
|
34
|
+
:workers => []
|
35
|
+
}.merge(opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_processor(opts = {})
|
39
|
+
classified_workers = { :workers => PikaQue::Util.worker_classes(opts[:workers]) }
|
40
|
+
processors << processor(opts.merge(classified_workers))
|
41
|
+
end
|
42
|
+
|
43
|
+
def processors
|
44
|
+
@processors ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
def config
|
48
|
+
PikaQue.config
|
49
|
+
end
|
50
|
+
|
23
51
|
end
|
24
52
|
end
|
data/lib/pika_que/util.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "dry/inflector"
|
2
|
+
|
1
3
|
module PikaQue
|
2
4
|
module Util
|
3
5
|
extend self
|
@@ -13,5 +15,35 @@ module PikaQue
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
18
|
+
def register_worker_class(worker_name, base_class, queue_name, queue_opts = {}, handler_class = nil, handler_opts = {}, local_config = {})
|
19
|
+
Object.const_set(worker_name, Class.new(base_class) do
|
20
|
+
from_queue queue_name, queue_opts
|
21
|
+
handle_with handler_class, handler_opts if handler_class
|
22
|
+
config local_config if local_config.any?
|
23
|
+
end
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def worker_classes(workers = [])
|
28
|
+
return [] if workers.nil?
|
29
|
+
|
30
|
+
workers.map do |worker|
|
31
|
+
if worker.is_a? Hash
|
32
|
+
if worker[:worker]
|
33
|
+
worker[:worker]
|
34
|
+
else
|
35
|
+
queue_name = worker[:queue_name] || worker[:queue]
|
36
|
+
"#{inflector.classify(inflector.underscore(queue_name))}Worker"
|
37
|
+
end
|
38
|
+
else
|
39
|
+
worker
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def inflector
|
45
|
+
@inflector ||= Dry::Inflector.new
|
46
|
+
end
|
47
|
+
|
16
48
|
end
|
17
49
|
end
|
data/lib/pika_que/version.rb
CHANGED
data/lib/pika_que.rb
CHANGED
@@ -42,14 +42,16 @@ module PikaQue
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def self.reporters
|
45
|
-
|
46
|
-
|
45
|
+
@reporters ||= [PikaQue::Reporters::LogReporter.new]
|
46
|
+
yield @reporters if block_given?
|
47
|
+
@reporters
|
47
48
|
end
|
48
49
|
|
49
50
|
def self.reset!
|
50
51
|
@config = nil
|
51
52
|
@connection = nil
|
52
53
|
@chain = nil
|
54
|
+
@reporters = nil
|
53
55
|
end
|
54
56
|
|
55
57
|
end
|
data/pika_que.gemspec
CHANGED
@@ -27,12 +27,13 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_dependency 'bunny', '~> 2.
|
30
|
+
spec.add_dependency 'bunny', '~> 2.19.0'
|
31
31
|
spec.add_dependency 'concurrent-ruby', '~> 1.0'
|
32
32
|
spec.add_dependency 'json', '~> 1.8'
|
33
|
+
spec.add_dependency 'dry-inflector'
|
33
34
|
|
34
|
-
spec.add_development_dependency "bundler", "~> 1.
|
35
|
-
spec.add_development_dependency "rake", "~>
|
36
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
37
|
-
spec.add_development_dependency "simplecov", "~> 0.
|
35
|
+
spec.add_development_dependency "bundler", "~> 2.1.4"
|
36
|
+
spec.add_development_dependency "rake", "~> 12.3.0"
|
37
|
+
spec.add_development_dependency "rspec", "~> 3.7.0"
|
38
|
+
spec.add_development_dependency "simplecov", "~> 0.16.1"
|
38
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pika_que
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dong Wook Koo
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.19.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.19.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: concurrent-ruby
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,62 +52,76 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dry-inflector
|
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
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
75
|
+
version: 2.1.4
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
82
|
+
version: 2.1.4
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rake
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
89
|
+
version: 12.3.0
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
96
|
+
version: 12.3.0
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: rspec
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
103
|
+
version: 3.7.0
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
110
|
+
version: 3.7.0
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: simplecov
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - "~>"
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
117
|
+
version: 0.16.1
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
124
|
+
version: 0.16.1
|
111
125
|
description: Ruby background processor for RabbitMQ.
|
112
126
|
email:
|
113
127
|
- dwkoogt@gmail.com
|
@@ -116,8 +130,10 @@ executables:
|
|
116
130
|
extensions: []
|
117
131
|
extra_rdoc_files: []
|
118
132
|
files:
|
133
|
+
- ".github/workflows/ruby.yml"
|
119
134
|
- ".gitignore"
|
120
135
|
- ".rspec"
|
136
|
+
- ".ruby-version"
|
121
137
|
- ".travis.yml"
|
122
138
|
- CODE_OF_CONDUCT.md
|
123
139
|
- Gemfile
|
@@ -129,9 +145,10 @@ files:
|
|
129
145
|
- examples/demo.rb
|
130
146
|
- examples/demo_conpriority.rb
|
131
147
|
- examples/demo_delay.rb
|
148
|
+
- examples/demo_dlx_retry.rb
|
149
|
+
- examples/demo_middleware.rb
|
132
150
|
- examples/demo_oneoff.rb
|
133
151
|
- examples/demo_priority.rb
|
134
|
-
- examples/demo_reporter.rb
|
135
152
|
- examples/demo_retry.rb
|
136
153
|
- examples/demo_worker.rb
|
137
154
|
- examples/dev_worker.rb
|
@@ -152,6 +169,7 @@ files:
|
|
152
169
|
- lib/pika_que/handlers.rb
|
153
170
|
- lib/pika_que/handlers/default_handler.rb
|
154
171
|
- lib/pika_que/handlers/delay_handler.rb
|
172
|
+
- lib/pika_que/handlers/dlx_retry_handler.rb
|
155
173
|
- lib/pika_que/handlers/error_handler.rb
|
156
174
|
- lib/pika_que/handlers/retry_handler.rb
|
157
175
|
- lib/pika_que/launcher.rb
|
@@ -161,9 +179,11 @@ files:
|
|
161
179
|
- lib/pika_que/metrics/null_metric.rb
|
162
180
|
- lib/pika_que/middleware/active_record.rb
|
163
181
|
- lib/pika_que/middleware/chain.rb
|
182
|
+
- lib/pika_que/middleware/request_store.rb
|
164
183
|
- lib/pika_que/processor.rb
|
165
184
|
- lib/pika_que/publisher.rb
|
166
185
|
- lib/pika_que/rails.rb
|
186
|
+
- lib/pika_que/rails_worker.rb
|
167
187
|
- lib/pika_que/reporters.rb
|
168
188
|
- lib/pika_que/reporters/log_reporter.rb
|
169
189
|
- lib/pika_que/runner.rb
|
@@ -191,8 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
211
|
- !ruby/object:Gem::Version
|
192
212
|
version: '0'
|
193
213
|
requirements: []
|
194
|
-
|
195
|
-
rubygems_version: 2.4.5.2
|
214
|
+
rubygems_version: 3.1.6
|
196
215
|
signing_key:
|
197
216
|
specification_version: 4
|
198
217
|
summary: Ruby background processor for RabbitMQ.
|
data/examples/demo_reporter.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
class DemoReporter
|
2
|
-
|
3
|
-
def initialize(opts = {})
|
4
|
-
STDOUT.sync = true
|
5
|
-
end
|
6
|
-
|
7
|
-
def call(worker, delivery_info, metadata, msg)
|
8
|
-
puts "entering middleware DemoReporter for msg: #{msg}"
|
9
|
-
begin
|
10
|
-
yield
|
11
|
-
rescue => e
|
12
|
-
puts "error caught in middleware DemoReporter for msg: #{msg}, error: #{e.message}"
|
13
|
-
raise e
|
14
|
-
ensure
|
15
|
-
puts "leaving middleware DemoReporter for msg: #{msg}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|