sanger_warren 0.2.0 → 0.3.0.pre.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile.lock +16 -16
- data/README.md +7 -0
- data/lefthook.yml +1 -1
- data/lib/warren/app/cli.rb +1 -0
- data/lib/warren/app/config.rb +10 -0
- data/lib/warren/app/consumer.rb +12 -0
- data/lib/warren/app/consumer_add.rb +15 -6
- data/lib/warren/app/consumer_start.rb +15 -0
- data/lib/warren/callback/broadcast_with_warren.rb +1 -1
- data/lib/warren/config/consumers.rb +24 -2
- data/lib/warren/delay_exchange.rb +85 -0
- data/lib/warren/den.rb +20 -5
- data/lib/warren/fox.rb +7 -3
- data/lib/warren/framework_adaptor/rails_adaptor.rb +51 -0
- data/lib/warren/handler.rb +16 -0
- data/lib/warren/handler/broadcast.rb +25 -6
- data/lib/warren/handler/log.rb +8 -0
- data/lib/warren/handler/test.rb +7 -8
- data/lib/warren/message.rb +2 -0
- data/lib/warren/message/full.rb +20 -0
- data/lib/warren/message/short.rb +8 -0
- data/lib/warren/message/simple.rb +9 -0
- data/lib/warren/subscriber/base.rb +33 -5
- data/lib/warren/subscription.rb +4 -4
- data/lib/warren/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 142ece2de33d995ad8eb33759edcefe6b265bd42ac71d0a13617a926a7cf0842
|
4
|
+
data.tar.gz: bdee8b37ee9b8b5dd8e3659f23ad129b1fb020d23c767458502eea5940672ac9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb6414532226afc214ba4d79e757315fe177d50e6ac94516d5e891fc86321fdafd301c08134846e83edf7ebaf5885eab6b0d43f2fd149b1cc7ca18e72f3e8c61
|
7
|
+
data.tar.gz: 318bd13ea663117d972f6351fcbcb2cd2758e0059d3e19828a2bf70a90575bcf38fd393038878d5fd253a6224ee3d8f14597843a252d14087097081ad13aeba2
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,26 @@ Unreleased section to make new releases easy.
|
|
5
5
|
|
6
6
|
## [Unreleased]
|
7
7
|
|
8
|
+
### Added
|
9
|
+
|
10
|
+
- Added support for delay exchanges to process messages after a fixed delay
|
11
|
+
- Increased documentation
|
12
|
+
- Added Warren::Message::Simple for wrapping just routing key and payload.
|
13
|
+
- Added optional worker_count to warren_consumers.yml to control number of worker threads
|
14
|
+
|
15
|
+
### Removed
|
16
|
+
|
17
|
+
- Warren::Handler::Test and Warren::Handler::Test::Channel no loner respond to
|
18
|
+
`add_exchange`. These methods were undocumented, and unused internally.
|
19
|
+
|
20
|
+
## Changed
|
21
|
+
|
22
|
+
- Messages must now implement {#headers}, although simply returning an empty
|
23
|
+
hash is sufficient.
|
24
|
+
- Subscriber templates now use the path 'app/warren/subscriber' rather than
|
25
|
+
'app/warren/subscribers' to correctly match class namespacing.
|
26
|
+
- 3 consumer worker threads will be spun up by default
|
27
|
+
|
8
28
|
## [0.2.0]
|
9
29
|
|
10
30
|
### Added
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sanger_warren (0.
|
4
|
+
sanger_warren (0.3.0.pre.rc1)
|
5
5
|
bunny (~> 2.17.0)
|
6
6
|
connection_pool (~> 2.2.0)
|
7
7
|
multi_json (~> 1.0)
|
@@ -10,13 +10,13 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
activemodel (5.2.
|
14
|
-
activesupport (= 5.2.
|
15
|
-
activerecord (5.2.
|
16
|
-
activemodel (= 5.2.
|
17
|
-
activesupport (= 5.2.
|
13
|
+
activemodel (5.2.6)
|
14
|
+
activesupport (= 5.2.6)
|
15
|
+
activerecord (5.2.6)
|
16
|
+
activemodel (= 5.2.6)
|
17
|
+
activesupport (= 5.2.6)
|
18
18
|
arel (>= 9.0)
|
19
|
-
activesupport (5.2.
|
19
|
+
activesupport (5.2.6)
|
20
20
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
21
21
|
i18n (>= 0.7, < 2)
|
22
22
|
minitest (~> 5.1)
|
@@ -30,16 +30,16 @@ GEM
|
|
30
30
|
concurrent-ruby (1.1.8)
|
31
31
|
connection_pool (2.2.5)
|
32
32
|
diff-lcs (1.4.4)
|
33
|
-
docile (1.
|
33
|
+
docile (1.4.0)
|
34
34
|
i18n (1.8.10)
|
35
35
|
concurrent-ruby (~> 1.0)
|
36
36
|
method_source (1.0.0)
|
37
37
|
minitest (5.14.4)
|
38
38
|
multi_json (1.15.0)
|
39
39
|
parallel (1.20.1)
|
40
|
-
parser (3.0.
|
40
|
+
parser (3.0.1.1)
|
41
41
|
ast (~> 2.4.1)
|
42
|
-
pry (0.14.
|
42
|
+
pry (0.14.1)
|
43
43
|
coderay (~> 1.1)
|
44
44
|
method_source (~> 1.0)
|
45
45
|
rainbow (3.0.0)
|
@@ -59,20 +59,20 @@ GEM
|
|
59
59
|
diff-lcs (>= 1.2.0, < 2.0)
|
60
60
|
rspec-support (~> 3.10.0)
|
61
61
|
rspec-support (3.10.2)
|
62
|
-
rubocop (1.
|
62
|
+
rubocop (1.15.0)
|
63
63
|
parallel (~> 1.10)
|
64
64
|
parser (>= 3.0.0.0)
|
65
65
|
rainbow (>= 2.2.2, < 4.0)
|
66
66
|
regexp_parser (>= 1.8, < 3.0)
|
67
67
|
rexml
|
68
|
-
rubocop-ast (>= 1.
|
68
|
+
rubocop-ast (>= 1.5.0, < 2.0)
|
69
69
|
ruby-progressbar (~> 1.7)
|
70
70
|
unicode-display_width (>= 1.4.0, < 3.0)
|
71
|
-
rubocop-ast (1.
|
72
|
-
parser (>=
|
71
|
+
rubocop-ast (1.5.0)
|
72
|
+
parser (>= 3.0.1.1)
|
73
73
|
rubocop-rake (0.5.1)
|
74
74
|
rubocop
|
75
|
-
rubocop-rspec (2.
|
75
|
+
rubocop-rspec (2.3.0)
|
76
76
|
rubocop (~> 1.0)
|
77
77
|
rubocop-ast (>= 1.1.0)
|
78
78
|
ruby-progressbar (1.11.0)
|
@@ -81,7 +81,7 @@ GEM
|
|
81
81
|
simplecov-html (~> 0.11)
|
82
82
|
simplecov_json_formatter (~> 0.1)
|
83
83
|
simplecov-html (0.12.3)
|
84
|
-
simplecov_json_formatter (0.1.
|
84
|
+
simplecov_json_formatter (0.1.3)
|
85
85
|
thor (1.1.0)
|
86
86
|
thread_safe (0.3.6)
|
87
87
|
tzinfo (1.2.9)
|
data/README.md
CHANGED
@@ -32,6 +32,13 @@ If using with a Rails app, you can simply run `bundle exec warren config` to
|
|
32
32
|
help generate a warren config file. Warren will automatically be initialize
|
33
33
|
on Rails start-up.
|
34
34
|
|
35
|
+
In rails 5 you will need to add the following to your `config/application.rb`
|
36
|
+
to ensure the auto-loader can find the subscribers.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
config.autoload_paths += %W{#{Rails.root}/app}
|
40
|
+
```
|
41
|
+
|
35
42
|
### Handler types
|
36
43
|
|
37
44
|
In development mode, warren is usually configured to log to the console only. If
|
data/lefthook.yml
CHANGED
@@ -7,7 +7,7 @@ pre-commit:
|
|
7
7
|
commands:
|
8
8
|
rubocop:
|
9
9
|
glob: '{*.{rb,arb,axlsx,builder,fcgi,gemfile,gemspec,god,jb,jbuilder,mspec,opal,pluginspec,podspec,rabl,rake,rbuild,rbw,rbx,ru,ruby,spec,thor,watchr},.irbrc,.pryrc,.simplecov,buildfile,Appraisals,Berksfile,Brewfile,Buildfile,Capfile,Cheffile,Dangerfile,Deliverfile,Fastfile,*Fastfile,Gemfile,Guardfile,Jarfile,Mavenfile,Podfile,Puppetfile,Rakefile,rakefile,Snapfile,Steepfile,Thorfile,Vagabondfile,Vagrantfile}'
|
10
|
-
run: rubocop --display-style-guide --extra-details --force-exclusion --parallel {staged_files} || (echo 'Run `lefthook run fix` to run
|
10
|
+
run: rubocop --display-style-guide --extra-details --force-exclusion --parallel {staged_files} || (echo 'Run `lefthook run fix` to run autocorrect on staged files only'; exit 1)
|
11
11
|
|
12
12
|
fix:
|
13
13
|
parallel: true
|
data/lib/warren/app/cli.rb
CHANGED
@@ -23,6 +23,7 @@ module Warren
|
|
23
23
|
desc: 'The path to the configuration file to generate'
|
24
24
|
option :exchange, type: :string,
|
25
25
|
desc: 'The RabbitMQ exchange to connect to'
|
26
|
+
# Invoked by `$ warren config` generates a `warren.yml` file.
|
26
27
|
def config
|
27
28
|
Warren::App::Config.invoke(self, path: options['path'], exchange: options['exchange'])
|
28
29
|
end
|
data/lib/warren/app/config.rb
CHANGED
@@ -42,6 +42,16 @@ module Warren
|
|
42
42
|
# circumstances should you commit sensitive information in the file.
|
43
43
|
TEMPLATE
|
44
44
|
|
45
|
+
# Triggers the configuration task. Primarily called by the Thor CLI.
|
46
|
+
# Will either use arguments passed in from the command line, or prompt the
|
47
|
+
# user for them if missing.
|
48
|
+
#
|
49
|
+
# @param shell [Thor::Shell::Basic] Thor shell instance for feedback
|
50
|
+
# @param path [String] Path to the `warren.yml` file
|
51
|
+
# @param exchange [String, nil] Name of the exchange to use, if passed in from CLI
|
52
|
+
#
|
53
|
+
# @return [Void]
|
54
|
+
#
|
45
55
|
def self.invoke(shell, path:, exchange: nil)
|
46
56
|
new(shell, path: path, exchange: exchange).invoke
|
47
57
|
end
|
data/lib/warren/app/consumer.rb
CHANGED
@@ -32,6 +32,14 @@ module Warren
|
|
32
32
|
option :path, type: :string,
|
33
33
|
default: Warren::Config::Consumers::DEFAULT_PATH,
|
34
34
|
desc: 'The path to the consumer configuration file to generate'
|
35
|
+
option :delay, type: :numeric,
|
36
|
+
desc: 'The delay (ms) on the delay queue. 0 to skip queue creation.'
|
37
|
+
# Invoked by `$ warren consumer add` adds a consumer to the `warren_consumers.yml`
|
38
|
+
#
|
39
|
+
# @param name [String, nil] Optional: Passed in from Command. The name of the consumer to create.
|
40
|
+
#
|
41
|
+
# @return [Void]
|
42
|
+
#
|
35
43
|
def add(name = nil)
|
36
44
|
say 'Adding a consumer'
|
37
45
|
Warren::App::ConsumerAdd.invoke(self, name, options)
|
@@ -44,6 +52,10 @@ module Warren
|
|
44
52
|
option :consumers, type: :array,
|
45
53
|
desc: 'The consumers to start. Defaults to all consumers',
|
46
54
|
banner: 'consumer_name other_consumer'
|
55
|
+
# Invoked by `$ warren consumer start`. Starts up the configured consumers
|
56
|
+
#
|
57
|
+
# @return [Void]
|
58
|
+
#
|
47
59
|
def start
|
48
60
|
say 'Starting consumers'
|
49
61
|
Warren::App::ConsumerStart.invoke(self, options)
|
@@ -7,7 +7,8 @@ module Warren
|
|
7
7
|
module App
|
8
8
|
# Handles the initial creation of the configuration object
|
9
9
|
class ConsumerAdd
|
10
|
-
|
10
|
+
# Default namespace for new Subscribers
|
11
|
+
SUBSCRIBER_NAMESPACE = %w[Warren Subscriber].freeze
|
11
12
|
|
12
13
|
attr_reader :name, :desc, :queue
|
13
14
|
|
@@ -46,6 +47,7 @@ module Warren
|
|
46
47
|
@name = name
|
47
48
|
@desc = options[:desc]
|
48
49
|
@queue = options[:queue]
|
50
|
+
@delay = options[:delay]
|
49
51
|
@config = Warren::Config::Consumers.new(options[:path])
|
50
52
|
@bindings = Warren::App::ExchangeConfig.parse(shell, options[:bindings])
|
51
53
|
end
|
@@ -69,7 +71,7 @@ module Warren
|
|
69
71
|
def subscribed_class
|
70
72
|
class_name = name.split(/[\s\-_]/).map(&:capitalize).join
|
71
73
|
|
72
|
-
|
74
|
+
[*SUBSCRIBER_NAMESPACE, class_name].join('::')
|
73
75
|
end
|
74
76
|
|
75
77
|
def check_name
|
@@ -98,6 +100,9 @@ module Warren
|
|
98
100
|
@desc ||= @shell.ask 'Provide an optional description: '
|
99
101
|
@queue ||= @shell.ask 'Provide the name of the queue to connect to: '
|
100
102
|
@bindings ||= gather_bindings
|
103
|
+
@delay ||= @shell.ask(
|
104
|
+
'Create a delay queue? Specify delay in milliseconds to create; set to 0 or leave blank to skip.'
|
105
|
+
).to_i
|
101
106
|
nil
|
102
107
|
end
|
103
108
|
|
@@ -106,16 +111,20 @@ module Warren
|
|
106
111
|
end
|
107
112
|
|
108
113
|
def write_configuration
|
109
|
-
@config.add_consumer(
|
114
|
+
@config.add_consumer(
|
115
|
+
@name, desc: @desc, queue: @queue,
|
116
|
+
bindings: @bindings, subscribed_class: subscribed_class,
|
117
|
+
delay: @delay
|
118
|
+
)
|
110
119
|
@config.save
|
111
120
|
end
|
112
121
|
|
113
122
|
def write_subscriber
|
114
|
-
@shell.template('subscriber.tt',
|
123
|
+
@shell.template('subscriber.tt', subscriber_path, context: binding)
|
115
124
|
end
|
116
125
|
|
117
|
-
def
|
118
|
-
"app
|
126
|
+
def subscriber_path
|
127
|
+
"#{['app', *SUBSCRIBER_NAMESPACE, @name.tr(' -', '_')].map(&:downcase).join('/')}.rb"
|
119
128
|
end
|
120
129
|
end
|
121
130
|
end
|
@@ -7,6 +7,17 @@ module Warren
|
|
7
7
|
module App
|
8
8
|
# Handles the initial creation of the configuration object
|
9
9
|
class ConsumerStart
|
10
|
+
#
|
11
|
+
# Starts up a warren client process for the configured consumers.
|
12
|
+
#
|
13
|
+
# @param shell [Thor::Shell::Basic] Thor shell instance for feedback
|
14
|
+
# @param options [Hash] Hash of command line arguments from Thor
|
15
|
+
# @option options [String] :path Path to the `warren_consumers.yml `file
|
16
|
+
# @option options [Array<String>] :consumers Array of configured consumers to start.
|
17
|
+
# Defaults to all consumers
|
18
|
+
#
|
19
|
+
# @return [Void]
|
20
|
+
#
|
10
21
|
def self.invoke(shell, options)
|
11
22
|
new(shell, options).invoke
|
12
23
|
end
|
@@ -17,6 +28,10 @@ module Warren
|
|
17
28
|
@consumers = options[:consumers]
|
18
29
|
end
|
19
30
|
|
31
|
+
#
|
32
|
+
# Starts up a warren client process for the configured consumers.
|
33
|
+
#
|
34
|
+
# @return [Void]
|
20
35
|
def invoke
|
21
36
|
Warren::Client.new(@config, consumers: @consumers).run
|
22
37
|
end
|
@@ -15,7 +15,7 @@ module Warren
|
|
15
15
|
# Creates the callback object
|
16
16
|
#
|
17
17
|
# @param handler [Warren::Handler] The handler to take the messaged
|
18
|
-
# @param message_class [Warren::Message] The
|
18
|
+
# @param message_class [Warren::Message] The adaptor to render the messages
|
19
19
|
#
|
20
20
|
def initialize(handler:, message_class: Warren::Message::Short)
|
21
21
|
@handler = handler
|
@@ -60,10 +60,12 @@ module Warren
|
|
60
60
|
# @param desc [String] Description of the consumer (Primarily for documentation)
|
61
61
|
# @param queue [String] Name of the queue to attach to
|
62
62
|
# @param bindings [Array<Hash>] Array of binding configuration hashed
|
63
|
+
# @param delay [Integer] Delay on the generated delay exchange
|
63
64
|
#
|
64
65
|
# @return [Hash] The consumer configuration hash
|
65
66
|
#
|
66
|
-
|
67
|
+
# rubocop:todo Metrics/ParameterLists
|
68
|
+
def add_consumer(name, desc:, queue:, bindings:, subscribed_class:, delay:)
|
67
69
|
dead_letter_exchange = "#{name}.dead-letters"
|
68
70
|
@config[name] = {
|
69
71
|
'desc' => desc,
|
@@ -71,9 +73,12 @@ module Warren
|
|
71
73
|
'subscribed_class' => subscribed_class,
|
72
74
|
# This smells wrong. I don't like the call back out to the App namespace
|
73
75
|
'dead_letters' => queue_config(dead_letter_exchange,
|
74
|
-
Warren::App::ExchangeConfig.default_dead_letter(dead_letter_exchange))
|
76
|
+
Warren::App::ExchangeConfig.default_dead_letter(dead_letter_exchange)),
|
77
|
+
'delay' => delay_exchange_configuration(ttl: delay, original_queue: queue, consumer_name: name),
|
78
|
+
'worker_count' => 3
|
75
79
|
}
|
76
80
|
end
|
81
|
+
# rubocop:enable Metrics/ParameterLists
|
77
82
|
|
78
83
|
private
|
79
84
|
|
@@ -86,6 +91,23 @@ module Warren
|
|
86
91
|
}
|
87
92
|
end
|
88
93
|
|
94
|
+
# rubocop:todo Metrics/MethodLength
|
95
|
+
def delay_exchange_configuration(ttl:, original_queue:, consumer_name:)
|
96
|
+
return {} if ttl.nil? || ttl.zero?
|
97
|
+
|
98
|
+
{
|
99
|
+
'exchange' => { 'name' => "#{consumer_name}.delay", 'options' => { type: 'fanout', durable: true } },
|
100
|
+
'bindings' => [{
|
101
|
+
'queue' => { 'name' => "#{consumer_name}.delay", 'options' => {
|
102
|
+
durable: true, arguments: {
|
103
|
+
'x-dead-letter-exchange' => '', 'x-message-ttl' => ttl, 'x-dead-letter-routing-key' => original_queue
|
104
|
+
}
|
105
|
+
} }, 'options' => {}
|
106
|
+
}]
|
107
|
+
}
|
108
|
+
end
|
109
|
+
# rubocop:enable Metrics/MethodLength
|
110
|
+
|
89
111
|
#
|
90
112
|
# Loads the configuration, should be a hash
|
91
113
|
#
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warren
|
4
|
+
# Configures and wraps up delay exchange on a Bunny Channel/Queue
|
5
|
+
# A delay exchange routes immediately onto a queue with a ttl
|
6
|
+
# once messages on this queue expire they are dead-lettered back onto
|
7
|
+
# to original exchange
|
8
|
+
# Note: This does not currently support the rabbitmq-delayed-message-exchange
|
9
|
+
# plugin.
|
10
|
+
class DelayExchange
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_reader :channel
|
14
|
+
|
15
|
+
#
|
16
|
+
# Great a new delay exchange. Handles queue creation, binding and attaching
|
17
|
+
# consumers to the queues
|
18
|
+
#
|
19
|
+
# @param channel [Warren::Handler::Broadcast::Channel] A channel on which to register queues
|
20
|
+
# @param config [Hash] queue configuration hash
|
21
|
+
#
|
22
|
+
def initialize(channel:, config:)
|
23
|
+
@channel = channel
|
24
|
+
@exchange_config = config&.fetch('exchange', nil)
|
25
|
+
@bindings = config&.fetch('bindings', [])
|
26
|
+
end
|
27
|
+
|
28
|
+
def_delegators :channel, :nack, :ack
|
29
|
+
|
30
|
+
# Ensures the queues and channels are set up to receive messages
|
31
|
+
# keys: additional routing_keys to bind
|
32
|
+
def activate!
|
33
|
+
establish_bindings!
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Post a message to the delay exchange.
|
38
|
+
#
|
39
|
+
# @param payload [String] The message payload
|
40
|
+
# @param routing_key [String] The routing key of the re-sent message
|
41
|
+
# @param headers [Hash] A hash of headers. Typically: { attempts: <Integer> }
|
42
|
+
# @option headers [Integer] :attempts The number of times the message has been processed
|
43
|
+
#
|
44
|
+
# @return [Void]
|
45
|
+
#
|
46
|
+
def publish(payload, routing_key:, headers: {})
|
47
|
+
raise StandardError, 'No delay queue configured' unless configured?
|
48
|
+
|
49
|
+
message = Warren::Message::Simple.new(routing_key, payload, headers)
|
50
|
+
channel.publish(message, exchange: exchange)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def configured?
|
56
|
+
@exchange_config&.key?('name')
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_binding(queue, options)
|
60
|
+
queue.bind(exchange, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def exchange
|
64
|
+
@exchange ||= channel.exchange(*@exchange_config.values_at('name', 'options'))
|
65
|
+
end
|
66
|
+
|
67
|
+
def queue(config)
|
68
|
+
channel.queue(*config.values_at('name', 'options'))
|
69
|
+
end
|
70
|
+
|
71
|
+
def establish_bindings!
|
72
|
+
@bindings.each do |binding_config|
|
73
|
+
queue = queue(binding_config['queue'])
|
74
|
+
transformed_options = merge_routing_key_prefix(binding_config['options'])
|
75
|
+
add_binding(queue, transformed_options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def merge_routing_key_prefix(options)
|
80
|
+
options.transform_values do |value|
|
81
|
+
format(value, routing_key_prefix: channel.routing_key_prefix)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/warren/den.rb
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
require 'bunny'
|
4
4
|
require 'warren/fox'
|
5
5
|
require 'warren/subscription'
|
6
|
+
require 'warren/delay_exchange'
|
6
7
|
|
7
8
|
module Warren
|
8
9
|
# A Den is in charge of creating a Fox from a consumer configuration
|
9
10
|
# Currently its pretty simple, but in future will also handle registration of
|
10
11
|
# delay and dead-letter queues/exchanges.
|
11
12
|
class Den
|
13
|
+
# The number of simultaneous workers generated by default
|
14
|
+
DEFAULT_WORKER_COUNT = 3
|
15
|
+
|
12
16
|
#
|
13
17
|
# Create a {Warren::Fox} work pool.
|
14
18
|
# @param app_name [String] The name of the application. Corresponds to the
|
@@ -34,9 +38,10 @@ module Warren
|
|
34
38
|
config = dead_letter_config
|
35
39
|
return unless config
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
Warren.handler.with_channel do |channel|
|
42
|
+
subscription = Warren::Subscription.new(channel: channel, config: config)
|
43
|
+
subscription.activate!
|
44
|
+
end
|
40
45
|
end
|
41
46
|
|
42
47
|
private
|
@@ -54,12 +59,18 @@ module Warren
|
|
54
59
|
# and while we *can* share channels between consumers it results in them
|
55
60
|
# sharing the same worker pool. This process lets us control workers on
|
56
61
|
# a per-queue basis. Currently that just means one worker per consumer.
|
57
|
-
channel = Warren.handler.new_channel
|
62
|
+
channel = Warren.handler.new_channel(worker_count: worker_count)
|
58
63
|
subscription = Warren::Subscription.new(channel: channel, config: queue_config)
|
64
|
+
delay = Warren::DelayExchange.new(channel: channel, config: delay_config)
|
59
65
|
Warren::Fox.new(name: @app_name,
|
60
66
|
subscription: subscription,
|
61
67
|
adaptor: @adaptor,
|
62
|
-
subscribed_class: subscribed_class
|
68
|
+
subscribed_class: subscribed_class,
|
69
|
+
delayed: delay)
|
70
|
+
end
|
71
|
+
|
72
|
+
def worker_count
|
73
|
+
consumer_config.fetch('worker_count', DEFAULT_WORKER_COUNT)
|
63
74
|
end
|
64
75
|
|
65
76
|
def queue_config
|
@@ -70,6 +81,10 @@ module Warren
|
|
70
81
|
consumer_config.fetch('dead_letters')
|
71
82
|
end
|
72
83
|
|
84
|
+
def delay_config
|
85
|
+
consumer_config.fetch('delay', nil)
|
86
|
+
end
|
87
|
+
|
73
88
|
def subscribed_class
|
74
89
|
Object.const_get(consumer_config.fetch('subscribed_class'))
|
75
90
|
end
|
data/lib/warren/fox.rb
CHANGED
@@ -20,7 +20,7 @@ module Warren
|
|
20
20
|
# Maximum wait time between database retries: 5 minutes
|
21
21
|
MAX_RECONNECT_DELAY = 60 * 5
|
22
22
|
|
23
|
-
attr_reader :state, :subscription, :consumer_tag
|
23
|
+
attr_reader :state, :subscription, :consumer_tag, :delayed
|
24
24
|
|
25
25
|
#
|
26
26
|
# Creates a fox, a RabbitMQ consumer.
|
@@ -30,10 +30,13 @@ module Warren
|
|
30
30
|
# @param name [String] The name of the consumer
|
31
31
|
# @param subscription [Warren::Subscription] Describes the queue to subscribe to
|
32
32
|
# @param adaptor [#recovered?,#handle,#env] An adaptor to handle framework specifics
|
33
|
+
# @param subscribed_class [Warren::Subscriber::Base] The class to process received messages
|
34
|
+
# @param delayed [Warren::DelayExchange] The details handling delayed message broadcast
|
33
35
|
#
|
34
|
-
def initialize(name:, subscription:, adaptor:, subscribed_class:)
|
36
|
+
def initialize(name:, subscription:, adaptor:, subscribed_class:, delayed:)
|
35
37
|
@consumer_tag = "#{adaptor.env}_#{name}_#{Process.pid}"
|
36
38
|
@subscription = subscription
|
39
|
+
@delayed = delayed
|
37
40
|
@logger = Warren::LogTagger.new(logger: adaptor.logger, tag: "#{FOX} #{@consumer_tag}")
|
38
41
|
@adaptor = adaptor
|
39
42
|
@subscribed_class = subscribed_class
|
@@ -52,6 +55,7 @@ module Warren
|
|
52
55
|
def run!
|
53
56
|
starting!
|
54
57
|
subscription.activate! # Set up the queues
|
58
|
+
delayed.activate!
|
55
59
|
running! # Transition to running state
|
56
60
|
subscribe! # Subscribe to the queue
|
57
61
|
|
@@ -117,7 +121,7 @@ module Warren
|
|
117
121
|
end
|
118
122
|
end
|
119
123
|
|
120
|
-
# Cancels the consumer and
|
124
|
+
# Cancels the consumer and un-registers it
|
121
125
|
def unsubscribe!
|
122
126
|
info { 'Unsubscribing' }
|
123
127
|
@consumer&.cancel
|
@@ -1,6 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Warren
|
4
|
+
# Namespace for framework adaptors.
|
5
|
+
# A FrameworkAdaptor should implement the following instance methods:
|
6
|
+
#
|
7
|
+
## recovered? => Bool
|
8
|
+
# Indicates that any temporary issues (such as database connectivity problems)
|
9
|
+
# are resolved and consumers may restart.
|
10
|
+
#
|
11
|
+
## handle
|
12
|
+
#
|
13
|
+
# Wraps the processing of each message, is expected to `yield` to allow
|
14
|
+
# processing. May be responsible for handling connection pools, and
|
15
|
+
# framework-specific exceptions. Raising {Warren::Exceptions::TemporaryIssue}
|
16
|
+
# here will cause consumers to sleep until `recovered?` returns true.
|
17
|
+
#
|
18
|
+
## env => String
|
19
|
+
#
|
20
|
+
# Returns the current environment of the application.
|
21
|
+
#
|
22
|
+
## logger => Logger
|
23
|
+
#
|
24
|
+
# Returns your application logger. Is expected to be compatible with the
|
25
|
+
# standard library Logger class.
|
26
|
+
# @see https://ruby-doc.org/stdlib-2.7.0/libdoc/logger/rdoc/Logger.html
|
27
|
+
#
|
28
|
+
## load_application
|
29
|
+
#
|
30
|
+
# Called upon running `warren consumer start`. Should ensure your application
|
31
|
+
# is correctly loaded sufficiently for processing messages
|
32
|
+
#
|
4
33
|
module FrameworkAdaptor
|
5
34
|
# The RailsAdaptor provides error handling and application
|
6
35
|
# loading for Rails applications
|
@@ -33,6 +62,11 @@ module Warren
|
|
33
62
|
end
|
34
63
|
end
|
35
64
|
|
65
|
+
#
|
66
|
+
# Checks that the database has recovered to allow message processing
|
67
|
+
#
|
68
|
+
# @return [Bool] Returns true if the application has recovered
|
69
|
+
#
|
36
70
|
def recovered?
|
37
71
|
ActiveRecord::Base.connection.reconnect!
|
38
72
|
true
|
@@ -40,6 +74,14 @@ module Warren
|
|
40
74
|
false
|
41
75
|
end
|
42
76
|
|
77
|
+
#
|
78
|
+
# Checks ensures a database connection has been checked out before
|
79
|
+
# yielding to allow message processing. Rescues loss of the database
|
80
|
+
# connection and raises {Warren::Exceptions::TemporaryIssue} to send
|
81
|
+
# the consumers to sleep until it recovers.
|
82
|
+
#
|
83
|
+
# @return [Void]
|
84
|
+
#
|
43
85
|
def handle
|
44
86
|
with_connection do
|
45
87
|
yield
|
@@ -60,14 +102,23 @@ module Warren
|
|
60
102
|
ActiveRecord::Base.clear_active_connections!
|
61
103
|
end
|
62
104
|
|
105
|
+
# Returns the rails environment
|
106
|
+
#
|
107
|
+
# @return [ActiveSupport::StringInquirer] The rails environment
|
63
108
|
def env
|
64
109
|
Rails.env
|
65
110
|
end
|
66
111
|
|
112
|
+
# Returns the configured logger
|
113
|
+
#
|
114
|
+
# @return [Logger,ActiveSupport::Logger,...] The application logger
|
67
115
|
def logger
|
68
116
|
Rails.logger
|
69
117
|
end
|
70
118
|
|
119
|
+
# Triggers full loading of the rails application and dependencies
|
120
|
+
#
|
121
|
+
# @return [Void]
|
71
122
|
def load_application
|
72
123
|
$stdout.puts 'Loading application...'
|
73
124
|
require './config/environment'
|
data/lib/warren/handler.rb
CHANGED
@@ -7,6 +7,22 @@ module Warren
|
|
7
7
|
# A {Warren::Handler} provides an interface for sending messages to either
|
8
8
|
# a message queue, a log, or an internal store for testing purposes.
|
9
9
|
module Handler
|
10
|
+
#
|
11
|
+
# Generates a template for routing keys for the given prefix, or a template
|
12
|
+
# that returns the provided routing key if no prefix is supplied.
|
13
|
+
#
|
14
|
+
# @example With a prefix
|
15
|
+
# template = Warren::Handler.routing_key_template('example') # => 'example.%s'
|
16
|
+
# format(template, 'routing.key') #=> 'example.routing.key'
|
17
|
+
#
|
18
|
+
# @example Without a prefix
|
19
|
+
# template = Warren::Handler.routing_key_template(nil) # => '%s'
|
20
|
+
# format(template, 'routing.key') #=> 'routing.key'
|
21
|
+
#
|
22
|
+
# @param prefix [String, nil] The prefix to use in the template
|
23
|
+
#
|
24
|
+
# @return [String] A template for generating routing keys
|
25
|
+
#
|
10
26
|
def self.routing_key_template(prefix)
|
11
27
|
prefix ? "#{prefix}.%s" : '%s'
|
12
28
|
end
|
@@ -28,17 +28,36 @@ module Warren
|
|
28
28
|
@routing_key_template = Handler.routing_key_template(routing_key_prefix)
|
29
29
|
end
|
30
30
|
|
31
|
+
# Publishes `message` to the configured exchange
|
32
|
+
#
|
33
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
34
|
+
# @see Warren::Message::Full
|
35
|
+
#
|
36
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
37
|
+
#
|
31
38
|
def <<(message)
|
32
|
-
|
39
|
+
publish(message)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Publishes `message` to `exchange` (Defaults to configured exchange)
|
43
|
+
#
|
44
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
45
|
+
# @see Warren::Message::Full
|
46
|
+
# @param exchange [Bunny::Exchange] The exchange to publish to
|
47
|
+
#
|
48
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
49
|
+
#
|
50
|
+
def publish(message, exchange: configured_exchange)
|
51
|
+
exchange.publish(message.payload, routing_key: key_for(message), headers: message.headers)
|
33
52
|
self
|
34
53
|
end
|
35
54
|
|
36
55
|
private
|
37
56
|
|
38
|
-
def
|
57
|
+
def configured_exchange
|
39
58
|
raise StandardError, 'No exchange configured' if @exchange_name.nil?
|
40
59
|
|
41
|
-
@
|
60
|
+
@configured_exchange ||= exchange(@exchange_name, auto_delete: false, durable: true, type: :topic)
|
42
61
|
end
|
43
62
|
|
44
63
|
def key_for(message)
|
@@ -108,9 +127,9 @@ module Warren
|
|
108
127
|
self
|
109
128
|
end
|
110
129
|
|
111
|
-
def new_channel
|
112
|
-
Channel.new(session.create_channel(nil,
|
113
|
-
|
130
|
+
def new_channel(worker_count: 1)
|
131
|
+
Channel.new(session.create_channel(nil, worker_count), exchange: @exchange_name,
|
132
|
+
routing_key_prefix: @routing_key_prefix)
|
114
133
|
end
|
115
134
|
|
116
135
|
private
|
data/lib/warren/handler/log.rb
CHANGED
@@ -14,6 +14,13 @@ module Warren
|
|
14
14
|
@routing_key_template = routing_key_template
|
15
15
|
end
|
16
16
|
|
17
|
+
# Logs `message` to the configured logger
|
18
|
+
#
|
19
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
20
|
+
# @see Warren::Message::Full
|
21
|
+
#
|
22
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
23
|
+
#
|
17
24
|
def <<(message)
|
18
25
|
@logger.info "Published: #{key_for(message)}"
|
19
26
|
@logger.debug "Payload: #{message.payload}"
|
@@ -40,6 +47,7 @@ module Warren
|
|
40
47
|
end
|
41
48
|
end
|
42
49
|
|
50
|
+
# Small object to track exchange properties for logging purposes
|
43
51
|
Exchange = Struct.new(:name, :options)
|
44
52
|
|
45
53
|
# Queue class to provide extended logging in development mode
|
data/lib/warren/handler/test.rb
CHANGED
@@ -77,13 +77,16 @@ module Warren
|
|
77
77
|
@warren = warren
|
78
78
|
end
|
79
79
|
|
80
|
+
# Records `message` for testing purposes
|
81
|
+
#
|
82
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
83
|
+
# @see Warren::Message::Full
|
84
|
+
#
|
85
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
86
|
+
#
|
80
87
|
def <<(message)
|
81
88
|
@warren << message
|
82
89
|
end
|
83
|
-
|
84
|
-
def add_exchange(name, options)
|
85
|
-
@warren.add_exchange(name, options)
|
86
|
-
end
|
87
90
|
end
|
88
91
|
|
89
92
|
#
|
@@ -185,10 +188,6 @@ module Warren
|
|
185
188
|
@messages << message if @enabled
|
186
189
|
end
|
187
190
|
|
188
|
-
def add_exchange(name, options)
|
189
|
-
@exchanges << [name, options] if @enabled
|
190
|
-
end
|
191
|
-
|
192
191
|
private
|
193
192
|
|
194
193
|
def raise_if_not_tracking
|
data/lib/warren/message.rb
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
require_relative 'message/short'
|
4
4
|
require_relative 'message/full'
|
5
|
+
require_relative 'message/simple'
|
5
6
|
|
6
7
|
# Namespace to collect message formats
|
7
8
|
# A Warren compatible message must implement:
|
8
9
|
# routing_key: returns the routing_key for the message
|
9
10
|
# payload: returns the message payload
|
11
|
+
# headers: Returns a headers hash
|
10
12
|
#
|
11
13
|
# Additionally, if you wish to use the Message with the ActiveRecord
|
12
14
|
# helpers, then the initialize should take the ActiveRecord::Base object
|
data/lib/warren/message/full.rb
CHANGED
@@ -10,6 +10,13 @@ module Warren
|
|
10
10
|
@record = record
|
11
11
|
end
|
12
12
|
|
13
|
+
#
|
14
|
+
# The routing key that will be used for the message, not including the
|
15
|
+
# routing_key_prefix configured in warren.yml. If {#record} responds
|
16
|
+
# to `routing_key` will use that instead
|
17
|
+
#
|
18
|
+
# @return [String] The routing key.
|
19
|
+
#
|
13
20
|
def routing_key
|
14
21
|
if record.respond_to?(:routing_key)
|
15
22
|
record.routing_key
|
@@ -18,9 +25,22 @@ module Warren
|
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
28
|
+
#
|
29
|
+
# The payload of the message.
|
30
|
+
# @see https://github.com/intridea/multi_json
|
31
|
+
#
|
32
|
+
# @return [String] The message payload
|
21
33
|
def payload
|
22
34
|
MultiJson.dump(record)
|
23
35
|
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# For compatibility. Returns an empty hash.
|
39
|
+
#
|
40
|
+
# @return [{}] Empty hash
|
41
|
+
def headers
|
42
|
+
{}
|
43
|
+
end
|
24
44
|
end
|
25
45
|
end
|
26
46
|
end
|
data/lib/warren/message/short.rb
CHANGED
@@ -24,7 +24,8 @@ module Warren
|
|
24
24
|
# delegators (Supplied by Forwardable)
|
25
25
|
# Essentially syntax is:
|
26
26
|
# def_delegators <target>, *<methods_to_delegate>
|
27
|
-
def_delegators :fox, :subscription, :warn, :info, :error, :debug
|
27
|
+
def_delegators :fox, :subscription, :warn, :info, :error, :debug, :delayed
|
28
|
+
def_delegators :delivery_info, :routing_key, :delivery_tag
|
28
29
|
|
29
30
|
#
|
30
31
|
# Construct a basic subscriber for each received message. Call {#process}
|
@@ -69,6 +70,8 @@ module Warren
|
|
69
70
|
warn "Re-queue: #{payload}"
|
70
71
|
warn "Re-queue Exception: #{exception.message}"
|
71
72
|
raise_if_acknowledged
|
73
|
+
# nack arguments: delivery_tag, multiple, requeue
|
74
|
+
# http://reference.rubybunny.info/Bunny/Channel.html#nack-instance_method
|
72
75
|
subscription.nack(delivery_tag, false, true)
|
73
76
|
@acknowledged = true
|
74
77
|
warn 'Re-queue nacked'
|
@@ -90,18 +93,43 @@ module Warren
|
|
90
93
|
error 'Dead-letter nacked'
|
91
94
|
end
|
92
95
|
|
96
|
+
#
|
97
|
+
# Re-post the message to the delay exchange and acknowledges receipt of
|
98
|
+
# the original message. The delay exchange will return the messages to
|
99
|
+
# the original queue after a delay.
|
100
|
+
#
|
101
|
+
# @param exception [StandardError] The exception that has caused the
|
102
|
+
# message to require a delay
|
103
|
+
#
|
104
|
+
# @return [Void]
|
105
|
+
#
|
106
|
+
def delay(exception)
|
107
|
+
return dead_letter(exception) if attempt > max_retries
|
108
|
+
|
109
|
+
warn "Delay: #{payload}"
|
110
|
+
warn "Delay Exception: #{exception.message}"
|
111
|
+
# Publish the message to the delay queue
|
112
|
+
delayed.publish(payload, routing_key: routing_key, headers: { attempts: attempt + 1 })
|
113
|
+
# Acknowledge the original message
|
114
|
+
ack
|
115
|
+
end
|
116
|
+
|
93
117
|
private
|
94
118
|
|
119
|
+
def max_retries
|
120
|
+
30
|
121
|
+
end
|
122
|
+
|
123
|
+
def attempt
|
124
|
+
headers.fetch('attempts', 0)
|
125
|
+
end
|
126
|
+
|
95
127
|
def headers
|
96
128
|
# Annoyingly it appears that a message with no headers
|
97
129
|
# returns nil, not an empty hash
|
98
130
|
properties.headers || {}
|
99
131
|
end
|
100
132
|
|
101
|
-
def delivery_tag
|
102
|
-
delivery_info.delivery_tag
|
103
|
-
end
|
104
|
-
|
105
133
|
# Acknowledge the message as successfully processed.
|
106
134
|
# Will raise {Warren::MultipleAcknowledgements} if the message has been
|
107
135
|
# acknowledged or rejected already.
|
data/lib/warren/subscription.rb
CHANGED
@@ -16,9 +16,9 @@ module Warren
|
|
16
16
|
#
|
17
17
|
def initialize(channel:, config:)
|
18
18
|
@channel = channel
|
19
|
-
@queue_name = config
|
20
|
-
@queue_options = config
|
21
|
-
@bindings = config
|
19
|
+
@queue_name = config&.fetch('name')
|
20
|
+
@queue_options = config&.fetch('options')
|
21
|
+
@bindings = config&.fetch('bindings')
|
22
22
|
end
|
23
23
|
|
24
24
|
def_delegators :channel, :nack, :ack
|
@@ -58,7 +58,7 @@ module Warren
|
|
58
58
|
def queue
|
59
59
|
raise StandardError, 'No queue configured' if @queue_name.nil?
|
60
60
|
|
61
|
-
channel.queue(@queue_name, @queue_options)
|
61
|
+
@queue ||= channel.queue(@queue_name, @queue_options)
|
62
62
|
end
|
63
63
|
|
64
64
|
def establish_bindings!
|
data/lib/warren/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sanger_warren
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Glover
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- lib/warren/callback/broadcast_with_warren.rb
|
108
108
|
- lib/warren/client.rb
|
109
109
|
- lib/warren/config/consumers.rb
|
110
|
+
- lib/warren/delay_exchange.rb
|
110
111
|
- lib/warren/den.rb
|
111
112
|
- lib/warren/exceptions.rb
|
112
113
|
- lib/warren/fox.rb
|
@@ -121,6 +122,7 @@ files:
|
|
121
122
|
- lib/warren/message.rb
|
122
123
|
- lib/warren/message/full.rb
|
123
124
|
- lib/warren/message/short.rb
|
125
|
+
- lib/warren/message/simple.rb
|
124
126
|
- lib/warren/railtie.rb
|
125
127
|
- lib/warren/subscriber/base.rb
|
126
128
|
- lib/warren/subscription.rb
|
@@ -148,9 +150,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
148
150
|
version: 2.6.0
|
149
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
152
|
requirements:
|
151
|
-
- - "
|
153
|
+
- - ">"
|
152
154
|
- !ruby/object:Gem::Version
|
153
|
-
version:
|
155
|
+
version: 1.3.1
|
154
156
|
requirements: []
|
155
157
|
rubygems_version: 3.1.4
|
156
158
|
signing_key:
|