harmoniser 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd51302d5c886af771b66e4528054042a8922071669719ba625aac5d5c0dc503
4
- data.tar.gz: 2dd00c5d6699436fca25d6c721cba80518aa8baa967e3bca247af408b829582b
3
+ metadata.gz: f94dc99fc3550a6ad359ffed69a06652849e74317ef607bedbaadad9c09f44fa
4
+ data.tar.gz: 489bea79098e457c8391f4fe1949b00e1618818b67fbff1fceb40da89ee48da8
5
5
  SHA512:
6
- metadata.gz: 6f2f43e8bc13d89db74bc38da9e08b86773aea3582cf5df277c985df22a016aa22bc5f965243b3df451c9f22ff40bc16299aeece5b7d5d15d57b6a43fb8b2233
7
- data.tar.gz: e3768b89e31de5b74b27333a990247b416067acc6055e4a1ea15d3ad63560a91780d9f379ced069f0cc9c950907186d2e67cd5daaacfaaf4b4f64b8715804415
6
+ metadata.gz: 7b78d93705c81e6dc30ddba7dc02733476a40998568299df5b2742ef80c47f2fba6bfb03e1d272f69472030ab19b40276c23beba88505d40fe86c65685855efd
7
+ data.tar.gz: d0ac0e8502cc760826e22d446ae914eacdca0f8b01faec19dc52350f994cd5c1fb3d663e4d2465bec6c26c5bdbb22e9f9278d8eff60521c08147f8c6cb743425
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
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
+
3
22
  ## [0.9.0] - 2024-08-09
4
23
 
5
24
  ### Added
data/harmoniser.gemspec CHANGED
@@ -24,5 +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.22"
27
+ spec.add_runtime_dependency "bunny", "~> 2.23"
28
28
  end
@@ -10,22 +10,55 @@ module Harmoniser
10
10
  end
11
11
  end
12
12
 
13
- def create_channel
14
- channel = Harmoniser.connection.create_channel
15
- channel.on_error(&method(:on_error).to_proc)
16
- channel.on_uncaught_exception(&method(:on_uncaught_exception).to_proc)
17
- channel
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
- Harmoniser.logger.error("Default on_error handler executed for channel: method = `#{amq_method}`, exchanges = `#{channel.exchanges.keys}`, queues = `#{channel.queues.keys}`")
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
@@ -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
- Launcher
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.merge({logger: Harmoniser.logger})
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 ||= create_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
- connection_timout: 5,
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
@@ -0,0 +1,11 @@
1
+ require_relative "base"
2
+
3
+ module Harmoniser
4
+ module Launcher
5
+ class UnBounded < Base
6
+ def start_subscribers
7
+ @consumers = subscribers.map(&:harmoniser_subscriber_start)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,47 +1,14 @@
1
- module Harmoniser
2
- class Launcher
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
- private
14
-
15
- def boot_app
16
- if File.directory?(@configuration.require)
17
- load_rails
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
- # TODO - Frameworks like Rails which have autoload for development/test will not start any subscriber unless the files where subscribers are located are required explicitly. Since we premier production and the eager load ensures that every file is loaded, this approach works
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 => e
38
- @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)}`")
39
- end
40
-
41
- def load_file
42
- require @configuration.require
43
- rescue LoadError => e
44
- @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)}`")
45
12
  end
46
13
  end
47
14
  end
@@ -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
@@ -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
@@ -44,7 +44,7 @@ module Harmoniser
44
44
  end
45
45
 
46
46
  def raise_missing_exchange_definition
47
- raise MissingExchangeDefinition, "Please call the harmoniser_publisher class method at `#{const_get(:HARMONISER_PUBLISHER_CLASS)}` with the exchange_name that will be used for publishing"
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,7 +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.const_set(:HARMONISER_PUBLISHER_CLASS, base)
60
+ base.private_constant(:HARMONISER_PUBLISHER_MUTEX)
61
61
  base.extend(ClassMethods)
62
62
  end
63
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/includable"
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
- channel = Subscriber.create_channel
44
+ ch = channel || Subscriber.create_channel
34
45
  consumer = Bunny::Consumer.new(
35
- channel,
46
+ ch,
36
47
  @harmoniser_consumer_definition.queue_name,
37
- @harmoniser_consumer_definition.consumer_tag || channel.generate_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
@@ -71,16 +82,20 @@ module Harmoniser
71
82
  end
72
83
 
73
84
  def raise_missing_consumer_definition
74
- raise MissingConsumerDefinition, "Please call the harmoniser_subscriber class method at `#{const_get(:HARMONISER_SUBSCRIBER_CLASS)}` with the queue_name that will be used for subscribing"
85
+ raise MissingConsumerDefinition, "Please call the harmoniser_subscriber class method at `#{name}` with the queue_name that will be used for subscribing"
75
86
  end
76
87
  end
77
88
 
78
89
  class << self
79
90
  def included(base)
80
91
  base.const_set(:HARMONISER_SUBSCRIBER_MUTEX, Mutex.new)
81
- base.const_set(:HARMONISER_SUBSCRIBER_CLASS, base)
92
+ base.private_constant(:HARMONISER_SUBSCRIBER_MUTEX)
93
+ registry << base
82
94
  base.extend(ClassMethods)
83
- harmoniser_register_included(base)
95
+ end
96
+
97
+ def registry
98
+ @registry ||= Registry.new
84
99
  end
85
100
  end
86
101
  end
@@ -41,11 +41,12 @@ module Harmoniser
41
41
  end
42
42
 
43
43
  def declare
44
- channel = self.class.create_channel
45
- declare_exchanges(channel)
46
- declare_queues(channel)
47
- declare_bindings(channel)
48
- Harmoniser.connection.close
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
49
50
  end
50
51
 
51
52
  private
@@ -1,3 +1,3 @@
1
1
  module Harmoniser
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -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.9.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-08-09 00:00:00.000000000 Z
11
+ date: 2024-09-16 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: '2.22'
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.22'
26
+ version: '2.23'
27
27
  description: A declarative approach to communication with RabbitMQ that simplifies
28
28
  the integration of publishing and consuming messages.
29
29
  email:
@@ -49,15 +49,19 @@ files:
49
49
  - lib/harmoniser/connectable.rb
50
50
  - lib/harmoniser/connection.rb
51
51
  - lib/harmoniser/definition.rb
52
- - lib/harmoniser/includable.rb
53
52
  - lib/harmoniser/launcher.rb
53
+ - lib/harmoniser/launcher/base.rb
54
+ - lib/harmoniser/launcher/bounded.rb
55
+ - lib/harmoniser/launcher/unbounded.rb
54
56
  - lib/harmoniser/loggable.rb
55
57
  - lib/harmoniser/options.rb
56
58
  - lib/harmoniser/parser.rb
57
59
  - lib/harmoniser/publisher.rb
58
60
  - lib/harmoniser/subscriber.rb
61
+ - lib/harmoniser/subscriber/registry.rb
59
62
  - lib/harmoniser/topology.rb
60
63
  - lib/harmoniser/version.rb
64
+ - lib/harmoniser/work_pool_reporter.rb
61
65
  homepage: https://github.com/jollopre/harmoniser
62
66
  licenses:
63
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