harmoniser 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/harmoniser.gemspec +1 -4
- data/lib/harmoniser/channelable.rb +39 -6
- data/lib/harmoniser/cli.rb +12 -5
- data/lib/harmoniser/configuration.rb +3 -1
- data/lib/harmoniser/connectable.rb +13 -21
- data/lib/harmoniser/connection.rb +3 -9
- data/lib/harmoniser/launcher/base.rb +75 -0
- data/lib/harmoniser/launcher/bounded.rb +22 -0
- data/lib/harmoniser/launcher/unbounded.rb +11 -0
- data/lib/harmoniser/launcher.rb +8 -41
- data/lib/harmoniser/options.rb +5 -1
- data/lib/harmoniser/parser.rb +3 -0
- data/lib/harmoniser/publisher.rb +3 -2
- data/lib/harmoniser/subscriber/registry.rb +19 -0
- data/lib/harmoniser/subscriber.rb +27 -10
- data/lib/harmoniser/topology.rb +9 -5
- data/lib/harmoniser/version.rb +1 -1
- data/lib/harmoniser/work_pool_reporter.rb +48 -0
- metadata +9 -47
- data/lib/harmoniser/includable.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f94dc99fc3550a6ad359ffed69a06652849e74317ef607bedbaadad9c09f44fa
|
4
|
+
data.tar.gz: 489bea79098e457c8391f4fe1949b00e1618818b67fbff1fceb40da89ee48da8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b78d93705c81e6dc30ddba7dc02733476a40998568299df5b2742ef80c47f2fba6bfb03e1d272f69472030ab19b40276c23beba88505d40fe86c65685855efd
|
7
|
+
data.tar.gz: d0ac0e8502cc760826e22d446ae914eacdca0f8b01faec19dc52350f994cd5c1fb3d663e4d2465bec6c26c5bdbb22e9f9278d8eff60521c08147f8c6cb743425
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,37 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [0.10.0] - 2024-09-16
|
4
|
+
|
5
|
+
### Added
|
6
|
+
- Add a concurrency option to the CLI. By default, concurrency is unbounded, i.e., each subscriber
|
7
|
+
has its own thread dedicated to processing messages
|
8
|
+
- Introduce Harmoniser::Subscriber::Registry for holding references to classes that include
|
9
|
+
Harmoniser::Subscriber
|
10
|
+
- Kill the process when an ACK timeout is received through any channel. The process terminates with
|
11
|
+
exit code 138
|
12
|
+
- Add a 25-second timeout to shut down the process. This is only applicable to processes using the
|
13
|
+
concurrency option
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
- Update Bunny to the latest version, i.e., 2.23
|
17
|
+
- Cancel subscribers and connection is done within the Launcher context instead of relying on
|
18
|
+
at_exit hook
|
19
|
+
- Prevent the connection from being closed after Topology#declare finishes; only the channel used
|
20
|
+
is closed
|
21
|
+
|
22
|
+
## [0.9.0] - 2024-08-09
|
23
|
+
|
24
|
+
### Added
|
25
|
+
- Add debug log when a message is received by a subscriber
|
26
|
+
- Add error_class, error_message and error_backtrace for `--require` option from cli
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
- Amend debug log when a message is published so that exchange name is included
|
30
|
+
- Improve error message for MissingExchangeDefinition and
|
31
|
+
MissingConsumerDefinition
|
32
|
+
- Define dev dependencies through Gemfile instead of gemspec
|
33
|
+
- Changed Topology methods to return self so that Topology definition becomes chainable
|
34
|
+
|
3
35
|
## [0.8.1] - 2024-04-08
|
4
36
|
|
5
37
|
### Fixed
|
data/harmoniser.gemspec
CHANGED
@@ -24,8 +24,5 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.executables = ["harmoniser"]
|
25
25
|
spec.require_paths = ["lib"]
|
26
26
|
|
27
|
-
spec.add_runtime_dependency "bunny", "~> 2.
|
28
|
-
spec.add_development_dependency "rake", "~> 13.0"
|
29
|
-
spec.add_development_dependency "rspec", "~> 3"
|
30
|
-
spec.add_development_dependency "standardrb", "~> 1.0"
|
27
|
+
spec.add_runtime_dependency "bunny", "~> 2.23"
|
31
28
|
end
|
@@ -10,22 +10,55 @@ module Harmoniser
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
def create_channel
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
def create_channel(consumer_pool_size: 1, consumer_pool_shutdown_timeout: 60)
|
14
|
+
connection
|
15
|
+
.create_channel(nil, consumer_pool_size, false, consumer_pool_shutdown_timeout)
|
16
|
+
.tap do |channel|
|
17
|
+
attach_callbacks(channel)
|
18
|
+
end
|
18
19
|
end
|
19
20
|
|
20
21
|
private
|
21
22
|
|
23
|
+
def connection
|
24
|
+
Harmoniser.connection
|
25
|
+
end
|
26
|
+
|
27
|
+
def attach_callbacks(channel)
|
28
|
+
channel.on_error(&method(:on_error).to_proc)
|
29
|
+
channel.on_uncaught_exception(&method(:on_uncaught_exception).to_proc)
|
30
|
+
end
|
31
|
+
|
22
32
|
def on_error(channel, amq_method)
|
23
|
-
|
33
|
+
attributes = {
|
34
|
+
amq_method: amq_method,
|
35
|
+
exchanges: channel.exchanges.keys,
|
36
|
+
queues: channel.consumers.values.map(&:queue)
|
37
|
+
}
|
38
|
+
|
39
|
+
if amq_method.is_a?(AMQ::Protocol::Channel::Close)
|
40
|
+
attributes[:reply_code] = amq_method.reply_code
|
41
|
+
attributes[:reply_text] = amq_method.reply_text
|
42
|
+
end
|
43
|
+
|
44
|
+
stringified_attributes = attributes.map { |k, v| "#{k} = `#{v}`" }.join(", ")
|
45
|
+
Harmoniser.logger.error("Default on_error handler executed for channel: #{stringified_attributes}")
|
46
|
+
maybe_kill_process(amq_method)
|
24
47
|
end
|
25
48
|
|
26
49
|
def on_uncaught_exception(error, consumer)
|
27
50
|
Harmoniser.logger.error("Default on_uncaught_exception handler executed for channel: error_class = `#{error.class}`, error_message = `#{error.message}`, error_backtrace = `#{error.backtrace&.first(5)}, queue = `#{consumer.queue}`")
|
28
51
|
end
|
52
|
+
|
53
|
+
def maybe_kill_process(amq_method)
|
54
|
+
Process.kill("USR1", Process.pid) if ack_timed_out?(amq_method) && Harmoniser.server?
|
55
|
+
end
|
56
|
+
|
57
|
+
def ack_timed_out?(amq_method)
|
58
|
+
return false unless amq_method.is_a?(AMQ::Protocol::Channel::Close)
|
59
|
+
|
60
|
+
amq_method.reply_text =~ /delivery acknowledgement on channel \d+ timed out/
|
61
|
+
end
|
29
62
|
end
|
30
63
|
|
31
64
|
class << self
|
data/lib/harmoniser/cli.rb
CHANGED
@@ -5,11 +5,13 @@ require "harmoniser/launcher"
|
|
5
5
|
|
6
6
|
module Harmoniser
|
7
7
|
class CLI
|
8
|
+
class SigUsr1 < StandardError; end
|
8
9
|
include Singleton
|
9
10
|
|
10
11
|
SIGNAL_HANDLERS = {
|
11
12
|
"INT" => lambda { |cli, signal| raise Interrupt },
|
12
|
-
"TERM" => lambda { |cli, signal| raise Interrupt }
|
13
|
+
"TERM" => lambda { |cli, signal| raise Interrupt },
|
14
|
+
"USR1" => lambda { |cli, signal| raise SigUsr1 }
|
13
15
|
}
|
14
16
|
SIGNAL_HANDLERS.default = lambda { |cli, signal| cli.logger.info("Default signal handler executed since there is no handler defined: signal = `#{signal}`") }
|
15
17
|
|
@@ -22,6 +24,7 @@ module Harmoniser
|
|
22
24
|
|
23
25
|
def call
|
24
26
|
parse_options
|
27
|
+
@launcher = Launcher.call(configuration: @configuration, logger: @logger)
|
25
28
|
run
|
26
29
|
end
|
27
30
|
|
@@ -37,7 +40,7 @@ module Harmoniser
|
|
37
40
|
def define_signals
|
38
41
|
@read_io, @write_io = IO.pipe
|
39
42
|
|
40
|
-
["INT", "TERM"].each do |sig|
|
43
|
+
["INT", "TERM", "USR1"].each do |sig|
|
41
44
|
Signal.trap(sig) do
|
42
45
|
@write_io.puts(sig)
|
43
46
|
end
|
@@ -45,9 +48,7 @@ module Harmoniser
|
|
45
48
|
end
|
46
49
|
|
47
50
|
def run
|
48
|
-
|
49
|
-
.new(configuration: configuration, logger: logger)
|
50
|
-
.start
|
51
|
+
@launcher.start
|
51
52
|
|
52
53
|
define_signals
|
53
54
|
|
@@ -58,7 +59,13 @@ module Harmoniser
|
|
58
59
|
rescue Interrupt
|
59
60
|
@write_io.close
|
60
61
|
@read_io.close
|
62
|
+
@launcher.stop
|
61
63
|
exit(0)
|
64
|
+
rescue SigUsr1
|
65
|
+
@write_io.close
|
66
|
+
@read_io.close
|
67
|
+
@launcher.stop
|
68
|
+
exit(128 + 10)
|
62
69
|
end
|
63
70
|
|
64
71
|
def handle_signal(signal)
|
@@ -9,7 +9,7 @@ module Harmoniser
|
|
9
9
|
include Connectable
|
10
10
|
|
11
11
|
attr_reader :logger, :options
|
12
|
-
def_delegators :options, :environment, :require, :verbose
|
12
|
+
def_delegators :options, :concurrency, :environment, :require, :verbose, :timeout
|
13
13
|
|
14
14
|
def initialize
|
15
15
|
@logger = Harmoniser.logger
|
@@ -33,8 +33,10 @@ module Harmoniser
|
|
33
33
|
|
34
34
|
def default_options
|
35
35
|
{
|
36
|
+
concurrency: Float::INFINITY,
|
36
37
|
environment: ENV.fetch("RAILS_ENV", ENV.fetch("RACK_ENV", "production")),
|
37
38
|
require: ".",
|
39
|
+
timeout: 25,
|
38
40
|
verbose: false
|
39
41
|
}
|
40
42
|
end
|
@@ -5,7 +5,18 @@ module Harmoniser
|
|
5
5
|
MUTEX = Mutex.new
|
6
6
|
|
7
7
|
def connection_opts
|
8
|
-
@connection_opts ||= Connection::DEFAULT_CONNECTION_OPTS
|
8
|
+
@connection_opts ||= Connection::DEFAULT_CONNECTION_OPTS
|
9
|
+
.merge({
|
10
|
+
logger: Harmoniser.logger,
|
11
|
+
recovery_attempt_started: proc {
|
12
|
+
stringified_connection = connection.to_s
|
13
|
+
Harmoniser.logger.info("Recovery attempt started: connection = `#{stringified_connection}`")
|
14
|
+
},
|
15
|
+
recovery_completed: proc {
|
16
|
+
stringified_connection = connection.to_s
|
17
|
+
Harmoniser.logger.info("Recovery completed: connection = `#{stringified_connection}`")
|
18
|
+
}
|
19
|
+
})
|
9
20
|
end
|
10
21
|
|
11
22
|
def connection_opts=(opts)
|
@@ -16,7 +27,7 @@ module Harmoniser
|
|
16
27
|
|
17
28
|
def connection
|
18
29
|
MUTEX.synchronize do
|
19
|
-
@connection ||=
|
30
|
+
@connection ||= Connection.new(connection_opts)
|
20
31
|
@connection.start unless @connection.open? || @connection.recovering_from_network_failure?
|
21
32
|
@connection
|
22
33
|
end
|
@@ -25,24 +36,5 @@ module Harmoniser
|
|
25
36
|
def connection?
|
26
37
|
!!defined?(@connection)
|
27
38
|
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def create_connection
|
32
|
-
at_exit(&method(:at_exit_handler).to_proc)
|
33
|
-
Connection.new(connection_opts)
|
34
|
-
end
|
35
|
-
|
36
|
-
def at_exit_handler
|
37
|
-
logger = Harmoniser.logger
|
38
|
-
|
39
|
-
logger.info("Shutting down!")
|
40
|
-
if connection? && @connection.open?
|
41
|
-
logger.info("Connection will be closed: connection = `#{@connection}`")
|
42
|
-
@connection.close
|
43
|
-
logger.info("Connection closed: connection = `#{@connection}`")
|
44
|
-
end
|
45
|
-
logger.info("Bye!")
|
46
|
-
end
|
47
39
|
end
|
48
40
|
end
|
@@ -7,19 +7,11 @@ module Harmoniser
|
|
7
7
|
|
8
8
|
DEFAULT_CONNECTION_OPTS = {
|
9
9
|
connection_name: "harmoniser@#{VERSION}",
|
10
|
-
|
10
|
+
connection_timeout: 5,
|
11
11
|
host: "127.0.0.1",
|
12
12
|
password: "guest",
|
13
13
|
port: 5672,
|
14
14
|
read_timeout: 5,
|
15
|
-
recovery_attempt_started: proc {
|
16
|
-
stringified_connection = Harmoniser.connection.to_s
|
17
|
-
Harmoniser.logger.info("Recovery attempt started: connection = `#{stringified_connection}`")
|
18
|
-
},
|
19
|
-
recovery_completed: proc {
|
20
|
-
stringified_connection = Harmoniser.connection.to_s
|
21
|
-
Harmoniser.logger.info("Recovery completed: connection = `#{stringified_connection}`")
|
22
|
-
},
|
23
15
|
tls_silence_warnings: true,
|
24
16
|
username: "guest",
|
25
17
|
verify_peer: false,
|
@@ -37,6 +29,7 @@ module Harmoniser
|
|
37
29
|
"<#{self.class.name}>: #{user}@#{host}:#{port}, connection_name = `#{connection_name}`, vhost = `#{vhost}`"
|
38
30
|
end
|
39
31
|
|
32
|
+
# TODO Only perform retries when Harmoniser.server?
|
40
33
|
def start
|
41
34
|
retries = 0
|
42
35
|
begin
|
@@ -79,6 +72,7 @@ module Harmoniser
|
|
79
72
|
@bunny.vhost
|
80
73
|
end
|
81
74
|
|
75
|
+
# TODO Use Signal handler defined at Harmoniser::CLI instead of rescuing SignalException?
|
82
76
|
def with_signal_handler
|
83
77
|
yield if block_given?
|
84
78
|
rescue SignalException => e
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "harmoniser/subscriber"
|
2
|
+
require "harmoniser/subscriber/registry"
|
3
|
+
require "harmoniser/work_pool_reporter"
|
4
|
+
|
5
|
+
module Harmoniser
|
6
|
+
module Launcher
|
7
|
+
class Base
|
8
|
+
attr_reader :subscribers
|
9
|
+
|
10
|
+
def initialize(configuration:, logger:)
|
11
|
+
@configuration = configuration
|
12
|
+
@logger = logger
|
13
|
+
@subscribers = Subscriber.registry
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
boot_app
|
18
|
+
start_subscribers
|
19
|
+
@logger.info("Subscribers registered to consume messages from queues: klasses = `#{@subscribers}`")
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
@logger.info("Shutting down!")
|
24
|
+
maybe_close
|
25
|
+
@logger.info("Bye!")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def boot_app
|
31
|
+
if File.directory?(@configuration.require)
|
32
|
+
load_rails
|
33
|
+
else
|
34
|
+
load_file
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_rails
|
39
|
+
filepath = File.expand_path("#{@configuration.require}/config/environment.rb")
|
40
|
+
require filepath
|
41
|
+
rescue LoadError => e
|
42
|
+
@logger.warn("Error while requiring file within directory. No subscribers will run for this process: require = `#{@configuration.require}`, filepath = `#{filepath}`, error_class = `#{e.class}`, error_message = `#{e.message}`, error_backtrace = `#{e.backtrace&.first(5)}`")
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_file
|
46
|
+
require @configuration.require
|
47
|
+
rescue LoadError => e
|
48
|
+
@logger.warn("Error while requiring file. No subscribers will run for this process: require = `#{@configuration.require}`, error_class = `#{e.class}`, error_message = `#{e.message}`, error_backtrace = `#{e.backtrace&.first(5)}`")
|
49
|
+
end
|
50
|
+
|
51
|
+
def maybe_close
|
52
|
+
return unless @configuration.connection?
|
53
|
+
return unless @configuration.connection.open?
|
54
|
+
|
55
|
+
maybe_cancel_subscribers
|
56
|
+
report_work_pool
|
57
|
+
|
58
|
+
connection = @configuration.connection
|
59
|
+
@logger.info("Connection will be closed: connection = `#{connection}`")
|
60
|
+
connection.close
|
61
|
+
@logger.info("Connection closed: connection = `#{connection}`")
|
62
|
+
end
|
63
|
+
|
64
|
+
def maybe_cancel_subscribers
|
65
|
+
@logger.info("Subscribers will be cancelled from queues: klasses = `#{@subscribers}`")
|
66
|
+
@subscribers.each(&:harmoniser_subscriber_stop)
|
67
|
+
@logger.info("Subscribers cancelled: klasses = `#{@subscribers}`")
|
68
|
+
end
|
69
|
+
|
70
|
+
def report_work_pool
|
71
|
+
@logger.info("Stats about the work pool: work_pool_reporter = `#{WorkPoolReporter.new(consumers: @consumers)}`. Note: A backlog greater than zero means messages could be lost for subscribers configured with no_ack, i.e. automatic ack")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "harmoniser/channelable"
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module Harmoniser
|
5
|
+
module Launcher
|
6
|
+
class Bounded < Base
|
7
|
+
include Channelable
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def start_subscribers
|
12
|
+
@consumers = subscribers.map do |klass|
|
13
|
+
klass.harmoniser_subscriber_start(channel: channel)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def channel
|
18
|
+
@channel ||= self.class.create_channel(consumer_pool_size: @configuration.concurrency, consumer_pool_shutdown_timeout: @configuration.timeout)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/harmoniser/launcher.rb
CHANGED
@@ -1,47 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
def initialize(configuration:, logger:)
|
4
|
-
@configuration = configuration
|
5
|
-
@logger = logger
|
6
|
-
end
|
7
|
-
|
8
|
-
def start
|
9
|
-
boot_app
|
10
|
-
start_subscribers
|
11
|
-
end
|
1
|
+
require_relative "launcher/bounded"
|
2
|
+
require_relative "launcher/unbounded"
|
12
3
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
load_file
|
20
|
-
end
|
21
|
-
end
|
4
|
+
module Harmoniser
|
5
|
+
module Launcher
|
6
|
+
class << self
|
7
|
+
def call(configuration:, logger:)
|
8
|
+
return Bounded.new(configuration:, logger:) unless configuration.options.unbounded_concurrency?
|
22
9
|
|
23
|
-
|
24
|
-
def start_subscribers
|
25
|
-
klasses = Subscriber.harmoniser_included
|
26
|
-
klasses.each do |klass|
|
27
|
-
klass.harmoniser_subscriber_start
|
10
|
+
UnBounded.new(configuration:, logger:)
|
28
11
|
end
|
29
|
-
@logger.info("Subscribers registered to consume messages from queues: klasses = `#{klasses}`")
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def load_rails
|
35
|
-
filepath = File.expand_path("#{@configuration.require}/config/environment.rb")
|
36
|
-
require filepath
|
37
|
-
rescue LoadError
|
38
|
-
@logger.warn("Error while requiring file within directory. No subscribers will run for this process: require = `#{@configuration.require}`, filepath = `#{filepath}`")
|
39
|
-
end
|
40
|
-
|
41
|
-
def load_file
|
42
|
-
require @configuration.require
|
43
|
-
rescue LoadError
|
44
|
-
@logger.warn("Error while requiring file. No subscribers will run for this process: require = `#{@configuration.require}`")
|
45
12
|
end
|
46
13
|
end
|
47
14
|
end
|
data/lib/harmoniser/options.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
module Harmoniser
|
2
|
-
Options = Data.define(:environment, :require, :verbose) do
|
2
|
+
Options = Data.define(:concurrency, :environment, :require, :verbose, :timeout) do
|
3
3
|
def production?
|
4
4
|
environment == "production"
|
5
5
|
end
|
6
6
|
|
7
|
+
def unbounded_concurrency?
|
8
|
+
concurrency == Float::INFINITY
|
9
|
+
end
|
10
|
+
|
7
11
|
def verbose?
|
8
12
|
!!verbose
|
9
13
|
end
|
data/lib/harmoniser/parser.rb
CHANGED
@@ -8,6 +8,9 @@ module Harmoniser
|
|
8
8
|
@options = {}
|
9
9
|
@option_parser = OptionParser.new do |opts|
|
10
10
|
opts.banner = "harmoniser [options]"
|
11
|
+
opts.on "-c", "--concurrency INT", "Set the number of threads to use" do |arg|
|
12
|
+
@options[:concurrency] = Integer(arg)
|
13
|
+
end
|
11
14
|
opts.on "-e", "--environment ENV", "Set the application environment (defaults to inferred environment or 'production')" do |arg|
|
12
15
|
@options[:environment] = arg
|
13
16
|
end
|
data/lib/harmoniser/publisher.rb
CHANGED
@@ -21,7 +21,7 @@ module Harmoniser
|
|
21
21
|
const_get(:HARMONISER_PUBLISHER_MUTEX).synchronize do
|
22
22
|
harmoniser_exchange.publish(payload, opts)
|
23
23
|
end
|
24
|
-
Harmoniser.logger.debug { "Message published: payload = `#{payload}`, opts = `#{opts}`" }
|
24
|
+
Harmoniser.logger.debug { "Message published: exchange = `#{@harmoniser_exchange_definition.name}`, payload = `#{payload}`, opts = `#{opts}`" }
|
25
25
|
|
26
26
|
harmoniser_exchange
|
27
27
|
end
|
@@ -44,7 +44,7 @@ module Harmoniser
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def raise_missing_exchange_definition
|
47
|
-
raise MissingExchangeDefinition, "Please
|
47
|
+
raise MissingExchangeDefinition, "Please call the harmoniser_publisher class method at `#{name}` with the exchange_name that will be used for publishing"
|
48
48
|
end
|
49
49
|
|
50
50
|
def handle_return(exchange)
|
@@ -57,6 +57,7 @@ module Harmoniser
|
|
57
57
|
class << self
|
58
58
|
def included(base)
|
59
59
|
base.const_set(:HARMONISER_PUBLISHER_MUTEX, Mutex.new)
|
60
|
+
base.private_constant(:HARMONISER_PUBLISHER_MUTEX)
|
60
61
|
base.extend(ClassMethods)
|
61
62
|
end
|
62
63
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Harmoniser
|
4
|
+
module Subscriber
|
5
|
+
class Registry
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@klasses, :<<, :to_a, :each, :map
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@klasses = Set.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
to_a.map(&:harmoniser_subscriber_to_s).to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
require "harmoniser/channelable"
|
2
2
|
require "harmoniser/definition"
|
3
|
-
require "harmoniser/
|
3
|
+
require "harmoniser/subscriber/registry"
|
4
4
|
|
5
5
|
module Harmoniser
|
6
6
|
module Subscriber
|
7
7
|
class MissingConsumerDefinition < StandardError; end
|
8
8
|
include Channelable
|
9
|
-
include Includable
|
10
9
|
|
11
10
|
module ClassMethods
|
12
11
|
def harmoniser_subscriber(queue_name:, consumer_tag: nil, no_ack: true, exclusive: false, arguments: {})
|
@@ -19,22 +18,34 @@ module Harmoniser
|
|
19
18
|
)
|
20
19
|
end
|
21
20
|
|
22
|
-
def harmoniser_subscriber_start
|
21
|
+
def harmoniser_subscriber_start(channel: nil)
|
23
22
|
const_get(:HARMONISER_SUBSCRIBER_MUTEX).synchronize do
|
24
|
-
@harmoniser_consumer ||= create_consumer
|
23
|
+
@harmoniser_consumer ||= create_consumer(channel)
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
27
|
+
def harmoniser_subscriber_stop
|
28
|
+
return unless @harmoniser_consumer
|
29
|
+
return unless @harmoniser_consumer.channel.open?
|
30
|
+
|
31
|
+
@harmoniser_consumer.cancel
|
32
|
+
end
|
33
|
+
|
34
|
+
def harmoniser_subscriber_to_s
|
35
|
+
definition = @harmoniser_consumer_definition
|
36
|
+
"<#{name}>: queue_name = `#{definition.queue_name}`, no_ack = `#{definition.no_ack}`"
|
37
|
+
end
|
38
|
+
|
28
39
|
private
|
29
40
|
|
30
|
-
def create_consumer
|
41
|
+
def create_consumer(channel)
|
31
42
|
raise_missing_consumer_definition unless @harmoniser_consumer_definition
|
32
43
|
|
33
|
-
|
44
|
+
ch = channel || Subscriber.create_channel
|
34
45
|
consumer = Bunny::Consumer.new(
|
35
|
-
|
46
|
+
ch,
|
36
47
|
@harmoniser_consumer_definition.queue_name,
|
37
|
-
@harmoniser_consumer_definition.consumer_tag ||
|
48
|
+
@harmoniser_consumer_definition.consumer_tag || ch.generate_consumer_tag,
|
38
49
|
@harmoniser_consumer_definition.no_ack,
|
39
50
|
@harmoniser_consumer_definition.exclusive,
|
40
51
|
@harmoniser_consumer_definition.arguments
|
@@ -57,6 +68,7 @@ module Harmoniser
|
|
57
68
|
|
58
69
|
def handle_delivery(consumer)
|
59
70
|
consumer.on_delivery do |delivery_info, properties, payload|
|
71
|
+
Harmoniser.logger.debug { "Message received by a consumer: consumer_tag = `#{consumer.consumer_tag}, `payload = `#{payload}`, queue = `#{consumer.queue}`" }
|
60
72
|
if respond_to?(:on_delivery)
|
61
73
|
on_delivery(delivery_info, properties, payload)
|
62
74
|
else
|
@@ -70,15 +82,20 @@ module Harmoniser
|
|
70
82
|
end
|
71
83
|
|
72
84
|
def raise_missing_consumer_definition
|
73
|
-
raise MissingConsumerDefinition, "Please
|
85
|
+
raise MissingConsumerDefinition, "Please call the harmoniser_subscriber class method at `#{name}` with the queue_name that will be used for subscribing"
|
74
86
|
end
|
75
87
|
end
|
76
88
|
|
77
89
|
class << self
|
78
90
|
def included(base)
|
79
91
|
base.const_set(:HARMONISER_SUBSCRIBER_MUTEX, Mutex.new)
|
92
|
+
base.private_constant(:HARMONISER_SUBSCRIBER_MUTEX)
|
93
|
+
registry << base
|
80
94
|
base.extend(ClassMethods)
|
81
|
-
|
95
|
+
end
|
96
|
+
|
97
|
+
def registry
|
98
|
+
@registry ||= Registry.new
|
82
99
|
end
|
83
100
|
end
|
84
101
|
end
|
data/lib/harmoniser/topology.rb
CHANGED
@@ -19,6 +19,7 @@ module Harmoniser
|
|
19
19
|
name: name,
|
20
20
|
opts: opts
|
21
21
|
)
|
22
|
+
self
|
22
23
|
end
|
23
24
|
|
24
25
|
def add_queue(name, **opts)
|
@@ -26,6 +27,7 @@ module Harmoniser
|
|
26
27
|
name: name,
|
27
28
|
opts: opts
|
28
29
|
)
|
30
|
+
self
|
29
31
|
end
|
30
32
|
|
31
33
|
def add_binding(exchange_name, destination_name, destination_type = :queue, **opts)
|
@@ -35,14 +37,16 @@ module Harmoniser
|
|
35
37
|
destination_type: destination_type,
|
36
38
|
opts: opts
|
37
39
|
)
|
40
|
+
self
|
38
41
|
end
|
39
42
|
|
40
43
|
def declare
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
self.class.create_channel.tap do |ch|
|
45
|
+
declare_exchanges(ch)
|
46
|
+
declare_queues(ch)
|
47
|
+
declare_bindings(ch)
|
48
|
+
ch.close
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
private
|
data/lib/harmoniser/version.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Harmoniser
|
4
|
+
class WorkPoolReporter
|
5
|
+
def initialize(consumers:, logger: Harmoniser.logger)
|
6
|
+
@channels = build_channels(consumers)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"<#{self.class.name}>: #{channels_to_s}"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def channels_to_s
|
17
|
+
@channels.map do |(channel, queues)|
|
18
|
+
channel_info(channel, queues.to_a)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def channel_info(channel, queues)
|
23
|
+
work_pool = channel.work_pool
|
24
|
+
"<#{channel.id}>: backlog = `#{work_pool.backlog}`, running? = `#{work_pool.running?}`, queues = `#{queues}`"
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_channels(consumers)
|
28
|
+
initial = Hash.new { |hash, key| hash[key] = Set.new }
|
29
|
+
consumers.each_with_object(initial) do |consumer, acc|
|
30
|
+
acc[DecoratedChannel.new(consumer.channel)] << consumer.queue
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class DecoratedChannel < SimpleDelegator
|
35
|
+
def id
|
36
|
+
__getobj__.id
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash
|
40
|
+
id.hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def eql?(other)
|
44
|
+
other.is_a?(DecoratedChannel) && id == other.id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: harmoniser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jose Lloret
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -16,56 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.23'
|
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: '2.
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '13.0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '13.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '3'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '3'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: standardrb
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.0'
|
26
|
+
version: '2.23'
|
69
27
|
description: A declarative approach to communication with RabbitMQ that simplifies
|
70
28
|
the integration of publishing and consuming messages.
|
71
29
|
email:
|
@@ -91,15 +49,19 @@ files:
|
|
91
49
|
- lib/harmoniser/connectable.rb
|
92
50
|
- lib/harmoniser/connection.rb
|
93
51
|
- lib/harmoniser/definition.rb
|
94
|
-
- lib/harmoniser/includable.rb
|
95
52
|
- lib/harmoniser/launcher.rb
|
53
|
+
- lib/harmoniser/launcher/base.rb
|
54
|
+
- lib/harmoniser/launcher/bounded.rb
|
55
|
+
- lib/harmoniser/launcher/unbounded.rb
|
96
56
|
- lib/harmoniser/loggable.rb
|
97
57
|
- lib/harmoniser/options.rb
|
98
58
|
- lib/harmoniser/parser.rb
|
99
59
|
- lib/harmoniser/publisher.rb
|
100
60
|
- lib/harmoniser/subscriber.rb
|
61
|
+
- lib/harmoniser/subscriber/registry.rb
|
101
62
|
- lib/harmoniser/topology.rb
|
102
63
|
- lib/harmoniser/version.rb
|
64
|
+
- lib/harmoniser/work_pool_reporter.rb
|
103
65
|
homepage: https://github.com/jollopre/harmoniser
|
104
66
|
licenses:
|
105
67
|
- MIT
|
@@ -1,25 +0,0 @@
|
|
1
|
-
module Harmoniser
|
2
|
-
module Includable
|
3
|
-
MUTEX = Mutex.new
|
4
|
-
private_constant :MUTEX
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
def harmoniser_register_included(klass)
|
8
|
-
MUTEX.synchronize do
|
9
|
-
@harmoniser_included ||= Set.new
|
10
|
-
@harmoniser_included << klass
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def harmoniser_included
|
15
|
-
@harmoniser_included.to_a
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class << self
|
20
|
-
def included(base)
|
21
|
-
base.extend(ClassMethods)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|