harmoniser 0.12.0 → 0.14.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 +49 -0
- data/harmoniser.gemspec +1 -1
- data/lib/harmoniser/channel.rb +66 -0
- data/lib/harmoniser/configuration.rb +5 -12
- data/lib/harmoniser/connectable.rb +3 -42
- data/lib/harmoniser/connection.rb +9 -3
- data/lib/harmoniser/error_handler.rb +51 -0
- data/lib/harmoniser/launcher/base.rb +2 -4
- data/lib/harmoniser/launcher/bounded.rb +1 -1
- data/lib/harmoniser/mock/channel.rb +92 -0
- data/lib/harmoniser/mock/connection.rb +38 -0
- data/lib/harmoniser/mock.rb +56 -0
- data/lib/harmoniser/options.rb +17 -1
- data/lib/harmoniser/publisher.rb +6 -6
- data/lib/harmoniser/subscriber.rb +1 -1
- data/lib/harmoniser/topology.rb +6 -5
- data/lib/harmoniser/version.rb +1 -1
- metadata +12 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a7c3ac5b97ffe50dee2438808e152133893642092fee47a74e49f31e5440686
|
|
4
|
+
data.tar.gz: 156dbce5e8ba12711085e55d2a2b6f823259946d0df42d6cccd7bd29aeba0139
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c14b5e163b0d3863ae1e25db3dc7aaec5ac6debacfe6cb0148066b5154156619b3692a7ea1e72d03d27e407a6366ad93c879476e04e606f864522ba56ffec59
|
|
7
|
+
data.tar.gz: 546204550ed925fac2e177240ec3266a25d61d81d9034019f76653588af7141c797be2ed42d576000cb7de72699843333b59cfafdb3d50a80a7a31a9596e1172
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.14.0] - 2025-11-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Introduce Mock functionality for testing without RabbitMQ dependency. Usage:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
require "harmoniser/mock"
|
|
10
|
+
Harmoniser::Mock.mock!
|
|
11
|
+
|
|
12
|
+
# Your Publisher/Subscriber code works the same but without RabbitMQ
|
|
13
|
+
class TestPublisher
|
|
14
|
+
include Harmoniser::Publisher
|
|
15
|
+
harmoniser_publisher exchange_name: "my_exchange"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
exchange = TestPublisher.publish("message", routing_key: "test")
|
|
19
|
+
published_messages = exchange.published_messages # Access captured messages
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- Add examples for mock publishing and topology under `examples/` folder
|
|
23
|
+
- Create Channel wrapper (`Harmoniser::Channel`) to isolate RabbitMQ dependencies
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Remove explicit dependencies to Bunny classes except for Consumer, enabling better RabbitMQ isolation through a single, well-defined interface
|
|
27
|
+
- Use Channel wrapper everywhere except for subscriptions (which still require actual Channel instance for Bunny::Consumer)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## [0.13.0] - 2025-05-19
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- Introduce Harmoniser.configuration#on_error callback to handle errors that occur while running the application, (e.g. when a subscriber raises an error, or when a connection cannot be established to RabbitMQ). This method permits appending as many handlers as needed. Usage:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
Harmoniser.configuration do |config|
|
|
37
|
+
config.on_error do |error, ctx|
|
|
38
|
+
# error is the exception, i.e. StandardError class raised by the application whereas ctx is the context of the error passed as Hash. The ctx hash contains at least the key `description` which is a string describing the error.
|
|
39
|
+
|
|
40
|
+
puts "An error occurred: exception = `#{error.detailed_message}`; description = `#{ctx[:description]}`"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- Update Bunny to the latest version, i.e., 2.24
|
|
47
|
+
- Delegate into Bunny::Channel to cancel consumers and close channel through
|
|
48
|
+
Channel#cancel_consumers_before_closing!
|
|
49
|
+
- Change severity level from error to warning for errors occurring at channel level
|
|
50
|
+
|
|
51
|
+
|
|
3
52
|
## [0.12.0] - 2024-12-22
|
|
4
53
|
|
|
5
54
|
### Changed
|
data/harmoniser.gemspec
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
3
|
+
module Harmoniser
|
|
4
|
+
class Channel
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
def_delegators :@bunny_channel,
|
|
8
|
+
:exchange,
|
|
9
|
+
:queue,
|
|
10
|
+
:queue_bind
|
|
11
|
+
|
|
12
|
+
attr_reader :bunny_channel
|
|
13
|
+
|
|
14
|
+
def initialize(bunny_channel)
|
|
15
|
+
@bunny_channel = bunny_channel
|
|
16
|
+
after_initialize
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def after_initialize
|
|
22
|
+
bunny_channel.cancel_consumers_before_closing!
|
|
23
|
+
attach_callbacks
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def attach_callbacks
|
|
27
|
+
bunny_channel.on_error(&method(:on_error_callback).to_proc)
|
|
28
|
+
bunny_channel.on_uncaught_exception(&method(:on_uncaught_exception_callback).to_proc)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def on_error_callback(channel, amq_method)
|
|
32
|
+
attributes = {
|
|
33
|
+
amq_method: amq_method,
|
|
34
|
+
exchanges: channel.exchanges.keys,
|
|
35
|
+
queues: channel.consumers.values.map(&:queue)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if amq_method.is_a?(AMQ::Protocol::Channel::Close)
|
|
39
|
+
attributes[:reply_code] = amq_method.reply_code
|
|
40
|
+
attributes[:reply_text] = amq_method.reply_text
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
stringified_attributes = attributes.map { |k, v| "#{k} = `#{v}`" }.join(", ")
|
|
44
|
+
Harmoniser.logger.warn("Default on_error handler executed for channel: #{stringified_attributes}")
|
|
45
|
+
maybe_kill_process(amq_method)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def on_uncaught_exception_callback(error, consumer)
|
|
49
|
+
handle_error(error, {description: "Uncaught exception from consumer", arguments: consumer.arguments, channel_id: consumer.channel.id, consumer_tag: consumer.consumer_tag, exclusive: consumer.exclusive, no_ack: consumer.no_ack, queue: consumer.queue})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def maybe_kill_process(amq_method)
|
|
53
|
+
Process.kill("USR1", Process.pid) if ack_timed_out?(amq_method) && Harmoniser.server?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def ack_timed_out?(amq_method)
|
|
57
|
+
return false unless amq_method.is_a?(AMQ::Protocol::Channel::Close)
|
|
58
|
+
|
|
59
|
+
amq_method.reply_text =~ /delivery acknowledgement on channel \d+ timed out/
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def handle_error(exception, ctx)
|
|
63
|
+
Harmoniser.configuration.handle_error(exception, ctx)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "forwardable"
|
|
2
2
|
require "harmoniser/connection"
|
|
3
|
+
require "harmoniser/error_handler"
|
|
3
4
|
require "harmoniser/topology"
|
|
4
5
|
require "harmoniser/options"
|
|
5
6
|
|
|
@@ -7,14 +8,16 @@ module Harmoniser
|
|
|
7
8
|
class Configuration
|
|
8
9
|
extend Forwardable
|
|
9
10
|
|
|
10
|
-
attr_reader :logger, :options
|
|
11
|
+
attr_reader :error_handler, :logger, :options
|
|
11
12
|
def_delegators :options, :concurrency, :environment, :require, :verbose, :timeout
|
|
13
|
+
def_delegators :error_handler, :handle_error, :on_error
|
|
12
14
|
|
|
13
15
|
def initialize
|
|
14
16
|
@logger = Harmoniser.logger
|
|
15
|
-
@options = Options.new
|
|
17
|
+
@options = Options.new
|
|
16
18
|
set_logger_severity
|
|
17
19
|
@topology = Topology.new
|
|
20
|
+
@error_handler = ErrorHandler.default
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def connection_opts
|
|
@@ -40,16 +43,6 @@ module Harmoniser
|
|
|
40
43
|
|
|
41
44
|
private
|
|
42
45
|
|
|
43
|
-
def default_options
|
|
44
|
-
{
|
|
45
|
-
concurrency: Float::INFINITY,
|
|
46
|
-
environment: ENV.fetch("RAILS_ENV", ENV.fetch("RACK_ENV", "production")),
|
|
47
|
-
require: ".",
|
|
48
|
-
timeout: 25,
|
|
49
|
-
verbose: false
|
|
50
|
-
}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
46
|
def set_logger_severity
|
|
54
47
|
@logger.level = @options.verbose? ? Logger::DEBUG : Logger::INFO
|
|
55
48
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "harmoniser/connection"
|
|
2
|
+
require "harmoniser/channel"
|
|
2
3
|
|
|
3
4
|
module Harmoniser
|
|
4
5
|
module Connectable
|
|
@@ -7,7 +8,7 @@ module Harmoniser
|
|
|
7
8
|
module ClassMethods
|
|
8
9
|
def connection(configuration = Harmoniser.configuration)
|
|
9
10
|
MUTEX.synchronize do
|
|
10
|
-
@connection ||= Connection.new(configuration.connection_opts)
|
|
11
|
+
@connection ||= Connection.new(configuration.connection_opts, error_handler: configuration.error_handler)
|
|
11
12
|
@connection.start unless @connection.open? || @connection.recovering_from_network_failure?
|
|
12
13
|
@connection
|
|
13
14
|
end
|
|
@@ -20,47 +21,7 @@ module Harmoniser
|
|
|
20
21
|
def create_channel(consumer_pool_size: 1, consumer_pool_shutdown_timeout: 60)
|
|
21
22
|
connection
|
|
22
23
|
.create_channel(nil, consumer_pool_size, false, consumer_pool_shutdown_timeout)
|
|
23
|
-
.
|
|
24
|
-
attach_callbacks(channel)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def attach_callbacks(channel)
|
|
31
|
-
channel.on_error(&method(:on_error).to_proc)
|
|
32
|
-
channel.on_uncaught_exception(&method(:on_uncaught_exception).to_proc)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def on_error(channel, amq_method)
|
|
36
|
-
attributes = {
|
|
37
|
-
amq_method: amq_method,
|
|
38
|
-
exchanges: channel.exchanges.keys,
|
|
39
|
-
queues: channel.consumers.values.map(&:queue)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if amq_method.is_a?(AMQ::Protocol::Channel::Close)
|
|
43
|
-
attributes[:reply_code] = amq_method.reply_code
|
|
44
|
-
attributes[:reply_text] = amq_method.reply_text
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
stringified_attributes = attributes.map { |k, v| "#{k} = `#{v}`" }.join(", ")
|
|
48
|
-
Harmoniser.logger.error("Default on_error handler executed for channel: #{stringified_attributes}")
|
|
49
|
-
maybe_kill_process(amq_method)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def on_uncaught_exception(error, consumer)
|
|
53
|
-
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}`")
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def maybe_kill_process(amq_method)
|
|
57
|
-
Process.kill("USR1", Process.pid) if ack_timed_out?(amq_method) && Harmoniser.server?
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def ack_timed_out?(amq_method)
|
|
61
|
-
return false unless amq_method.is_a?(AMQ::Protocol::Channel::Close)
|
|
62
|
-
|
|
63
|
-
amq_method.reply_text =~ /delivery acknowledgement on channel \d+ timed out/
|
|
24
|
+
.yield_self { |bunny_channel| Channel.new(bunny_channel) }
|
|
64
25
|
end
|
|
65
26
|
end
|
|
66
27
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "forwardable"
|
|
2
2
|
require "bunny"
|
|
3
|
+
require "harmoniser/error_handler"
|
|
3
4
|
|
|
4
5
|
module Harmoniser
|
|
5
6
|
class Connection
|
|
@@ -21,7 +22,8 @@ module Harmoniser
|
|
|
21
22
|
|
|
22
23
|
def_delegators :@bunny, :create_channel, :open?, :recovering_from_network_failure?
|
|
23
24
|
|
|
24
|
-
def initialize(opts, logger: Harmoniser.logger)
|
|
25
|
+
def initialize(opts, error_handler: ErrorHandler.default, logger: Harmoniser.logger)
|
|
26
|
+
@error_handler = error_handler
|
|
25
27
|
@logger = logger
|
|
26
28
|
@bunny = Bunny.new(maybe_dynamic_opts(opts)).tap do |bunny|
|
|
27
29
|
attach_callbacks(bunny)
|
|
@@ -43,7 +45,7 @@ module Harmoniser
|
|
|
43
45
|
begin
|
|
44
46
|
@bunny.start
|
|
45
47
|
rescue => e
|
|
46
|
-
|
|
48
|
+
handle_error(e, {description: "Connection attempt failed", retries: retries})
|
|
47
49
|
sleep(1)
|
|
48
50
|
retries += 1
|
|
49
51
|
retry
|
|
@@ -56,7 +58,7 @@ module Harmoniser
|
|
|
56
58
|
@logger.info("Connection closed: connection = `#{self}`")
|
|
57
59
|
end
|
|
58
60
|
rescue => e
|
|
59
|
-
|
|
61
|
+
handle_error(e, {description: "Connection close failed"})
|
|
60
62
|
false
|
|
61
63
|
end
|
|
62
64
|
|
|
@@ -102,5 +104,9 @@ module Harmoniser
|
|
|
102
104
|
def vhost
|
|
103
105
|
@bunny.vhost
|
|
104
106
|
end
|
|
107
|
+
|
|
108
|
+
def handle_error(exception, ctx)
|
|
109
|
+
@error_handler.handle_error(exception, ctx)
|
|
110
|
+
end
|
|
105
111
|
end
|
|
106
112
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Harmoniser
|
|
2
|
+
class ErrorHandler
|
|
3
|
+
TIMEOUT = 25
|
|
4
|
+
DEFAULT_ERROR_HANDLER = ->(exception, ctx) do
|
|
5
|
+
Harmoniser.logger.error("Error handler called: exception = `#{exception.detailed_message}`, context = `#{ctx}`")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def default
|
|
10
|
+
@default ||= new([DEFAULT_ERROR_HANDLER])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(handlers = [])
|
|
15
|
+
@handlers = handlers
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_error(handler = nil, &block)
|
|
19
|
+
h = handler || block
|
|
20
|
+
raise ArgumentError, "Please, provide a handler or a block" if h.nil?
|
|
21
|
+
raise TypeError, "Handler must respond to call" unless h.respond_to?(:call)
|
|
22
|
+
|
|
23
|
+
@handlers << h
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def handle_error(exception, ctx = {})
|
|
28
|
+
coordinator = Thread::Queue.new
|
|
29
|
+
|
|
30
|
+
Thread.new do
|
|
31
|
+
handlers
|
|
32
|
+
.each { |handler| trigger_handler(handler, exception, ctx) }
|
|
33
|
+
.tap { coordinator.push(true) }
|
|
34
|
+
end.tap do |thread|
|
|
35
|
+
thread.report_on_exception = true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
!!coordinator.pop(timeout: TIMEOUT)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :handlers
|
|
44
|
+
|
|
45
|
+
def trigger_handler(handler, exception, ctx)
|
|
46
|
+
handler.call(exception, ctx)
|
|
47
|
+
rescue => e
|
|
48
|
+
warn "An error occurred while handling a previous error: #{e.detailed_message}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -61,15 +61,13 @@ module Harmoniser
|
|
|
61
61
|
def maybe_close_subscriber
|
|
62
62
|
return unless Subscriber.connection?
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
report_subscribers_to_cancel
|
|
65
65
|
report_work_pool
|
|
66
66
|
Subscriber.connection.close
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
def
|
|
69
|
+
def report_subscribers_to_cancel
|
|
70
70
|
@logger.info("Subscribers will be cancelled from queues: klasses = `#{@subscribers}`")
|
|
71
|
-
@subscribers.each(&:harmoniser_subscriber_stop)
|
|
72
|
-
@logger.info("Subscribers cancelled: klasses = `#{@subscribers}`")
|
|
73
71
|
end
|
|
74
72
|
|
|
75
73
|
def report_work_pool
|
|
@@ -12,7 +12,7 @@ module Harmoniser
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def channel
|
|
15
|
-
@channel ||= Subscriber.create_channel(consumer_pool_size: @configuration.concurrency, consumer_pool_shutdown_timeout: @configuration.timeout)
|
|
15
|
+
@channel ||= Subscriber.create_channel(consumer_pool_size: @configuration.concurrency, consumer_pool_shutdown_timeout: @configuration.timeout).bunny_channel
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Harmoniser
|
|
4
|
+
module Mock
|
|
5
|
+
class Channel
|
|
6
|
+
class MockExchange
|
|
7
|
+
attr_reader :name, :type, :opts
|
|
8
|
+
|
|
9
|
+
def initialize(name, opts = {})
|
|
10
|
+
@name = name
|
|
11
|
+
@type = opts[:type]
|
|
12
|
+
@opts = opts.except(:type)
|
|
13
|
+
@published_messages = []
|
|
14
|
+
@return_handler = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def publish(payload, opts = {})
|
|
18
|
+
@published_messages << {payload: payload, opts: opts}
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_return(&block)
|
|
23
|
+
@return_handler = block
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def published_messages
|
|
28
|
+
@published_messages.dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def reset!
|
|
32
|
+
@published_messages.clear
|
|
33
|
+
@return_handler = nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class MockQueue
|
|
38
|
+
attr_reader :name, :opts
|
|
39
|
+
|
|
40
|
+
def initialize(name, opts = {})
|
|
41
|
+
@name = name
|
|
42
|
+
@opts = opts
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize
|
|
47
|
+
@exchanges = {}
|
|
48
|
+
@queues = {}
|
|
49
|
+
@bindings = []
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def exchange(name, opts = {})
|
|
53
|
+
@exchanges[name] ||= MockExchange.new(name, opts)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def queue(name, opts = {})
|
|
57
|
+
@queues[name] ||= MockQueue.new(name, opts)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def queue_bind(destination_name, exchange_name, opts = {})
|
|
61
|
+
@bindings << {
|
|
62
|
+
destination_name: destination_name,
|
|
63
|
+
exchange_name: exchange_name,
|
|
64
|
+
opts: opts
|
|
65
|
+
}
|
|
66
|
+
true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def exchanges
|
|
70
|
+
@exchanges.dup
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def queues
|
|
74
|
+
@queues.dup
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def bindings
|
|
78
|
+
@bindings.dup
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def reset!
|
|
82
|
+
@exchanges.clear
|
|
83
|
+
@queues.clear
|
|
84
|
+
@bindings.clear
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def bunny_channel
|
|
88
|
+
raise "Cannot access bunny_channel in mock mode. Mock mode is intended for testing only and cannot be used when running Harmoniser as a server process."
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "harmoniser/mock/channel"
|
|
4
|
+
|
|
5
|
+
module Harmoniser
|
|
6
|
+
module Mock
|
|
7
|
+
class Connection
|
|
8
|
+
def initialize(opts = {}, error_handler: nil, logger: nil)
|
|
9
|
+
@opts = opts
|
|
10
|
+
@error_handler = error_handler
|
|
11
|
+
@logger = logger
|
|
12
|
+
@open = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_channel(id = nil, consumer_pool_size = 1, consumer_pool_ack = false, consumer_pool_shutdown_timeout = 60)
|
|
16
|
+
Channel.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def open?
|
|
20
|
+
@open
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def recovering_from_network_failure?
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start
|
|
28
|
+
@open = true
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def close
|
|
33
|
+
@open = false
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "harmoniser/mock/connection"
|
|
4
|
+
require "harmoniser/mock/channel"
|
|
5
|
+
require "harmoniser/connectable"
|
|
6
|
+
|
|
7
|
+
module Harmoniser
|
|
8
|
+
module Mock
|
|
9
|
+
@mocked = false
|
|
10
|
+
@prepended = false
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def mock!
|
|
14
|
+
unless @prepended
|
|
15
|
+
@prepended = true
|
|
16
|
+
Harmoniser::Connectable::ClassMethods.prepend(MockConnectableMethods)
|
|
17
|
+
end
|
|
18
|
+
@mocked = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def disable!
|
|
22
|
+
@mocked = false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def mocked?
|
|
26
|
+
@mocked
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def disabled?
|
|
30
|
+
!@mocked
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module MockConnectableMethods
|
|
35
|
+
def connection(configuration = Harmoniser.configuration)
|
|
36
|
+
return super unless Harmoniser::Mock.mocked?
|
|
37
|
+
|
|
38
|
+
Harmoniser::Connectable::MUTEX.synchronize do
|
|
39
|
+
@mock_connection ||= Harmoniser::Mock::Connection.new(configuration.connection_opts, error_handler: configuration.error_handler)
|
|
40
|
+
@mock_connection.start unless @mock_connection.open? || @mock_connection.recovering_from_network_failure?
|
|
41
|
+
@mock_connection
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def connection?
|
|
46
|
+
return super unless Harmoniser::Mock.mocked?
|
|
47
|
+
!!defined?(@mock_connection)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create_channel(consumer_pool_size: 1, consumer_pool_shutdown_timeout: 60)
|
|
51
|
+
return super unless Harmoniser::Mock.mocked?
|
|
52
|
+
connection.create_channel(nil, consumer_pool_size, false, consumer_pool_shutdown_timeout)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/harmoniser/options.rb
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
module Harmoniser
|
|
2
|
-
Options
|
|
2
|
+
class Options < Data.define(:concurrency, :environment, :require, :verbose, :timeout)
|
|
3
|
+
DEFAULT = {
|
|
4
|
+
concurrency: -> { Float::INFINITY },
|
|
5
|
+
environment: -> { ENV.fetch("RAILS_ENV", ENV.fetch("RACK_ENV", "production")) },
|
|
6
|
+
require: -> { "." },
|
|
7
|
+
timeout: -> { 25 },
|
|
8
|
+
verbose: -> { false }
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(**kwargs)
|
|
12
|
+
options = DEFAULT
|
|
13
|
+
.map { |k, v| [k, v.call] }
|
|
14
|
+
.to_h
|
|
15
|
+
.merge(kwargs)
|
|
16
|
+
super(**options)
|
|
17
|
+
end
|
|
18
|
+
|
|
3
19
|
def production?
|
|
4
20
|
environment == "production"
|
|
5
21
|
end
|
data/lib/harmoniser/publisher.rb
CHANGED
|
@@ -33,12 +33,12 @@ module Harmoniser
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def create_exchange
|
|
36
|
-
exchange =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
exchange = Publisher
|
|
37
|
+
.create_channel
|
|
38
|
+
.exchange(
|
|
39
|
+
@harmoniser_exchange_definition.name,
|
|
40
|
+
{type: @harmoniser_exchange_definition.type}.merge(@harmoniser_exchange_definition.opts)
|
|
41
|
+
)
|
|
42
42
|
handle_return(exchange)
|
|
43
43
|
exchange
|
|
44
44
|
end
|
|
@@ -41,7 +41,7 @@ module Harmoniser
|
|
|
41
41
|
def create_consumer(channel)
|
|
42
42
|
raise_missing_consumer_definition unless @harmoniser_consumer_definition
|
|
43
43
|
|
|
44
|
-
ch = channel || Subscriber.create_channel
|
|
44
|
+
ch = channel || Subscriber.create_channel.bunny_channel
|
|
45
45
|
consumer = Bunny::Consumer.new(
|
|
46
46
|
ch,
|
|
47
47
|
@harmoniser_consumer_definition.queue_name,
|
data/lib/harmoniser/topology.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Harmoniser
|
|
|
5
5
|
class Topology
|
|
6
6
|
include Connectable
|
|
7
7
|
|
|
8
|
-
attr_reader :bindings, :exchanges, :queues
|
|
8
|
+
attr_reader :bindings, :exchanges, :queues, :declared_channel
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
11
|
@bindings = Set.new
|
|
@@ -41,25 +41,26 @@ module Harmoniser
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def declare
|
|
44
|
-
self.class.create_channel.tap do |ch|
|
|
44
|
+
@declared_channel = self.class.create_channel.tap do |ch|
|
|
45
45
|
declare_exchanges(ch)
|
|
46
46
|
declare_queues(ch)
|
|
47
47
|
declare_bindings(ch)
|
|
48
|
-
ch.connection.close
|
|
49
48
|
end
|
|
49
|
+
self.class.connection.close
|
|
50
|
+
self
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
private
|
|
53
54
|
|
|
54
55
|
def declare_exchanges(channel)
|
|
55
56
|
exchanges.each do |exchange|
|
|
56
|
-
|
|
57
|
+
channel.exchange(exchange.name, {type: exchange.type}.merge(exchange.opts))
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def declare_queues(channel)
|
|
61
62
|
queues.each do |queue|
|
|
62
|
-
|
|
63
|
+
channel.queue(queue.name, queue.opts)
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
|
data/lib/harmoniser/version.rb
CHANGED
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.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jose Lloret
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-11-25 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.
|
|
19
|
+
version: '2.24'
|
|
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.
|
|
26
|
+
version: '2.24'
|
|
27
27
|
description: A declarative approach to communication with RabbitMQ that simplifies
|
|
28
28
|
the integration of publishing and consuming messages.
|
|
29
29
|
email:
|
|
@@ -42,17 +42,22 @@ files:
|
|
|
42
42
|
- bin/setup
|
|
43
43
|
- harmoniser.gemspec
|
|
44
44
|
- lib/harmoniser.rb
|
|
45
|
+
- lib/harmoniser/channel.rb
|
|
45
46
|
- lib/harmoniser/cli.rb
|
|
46
47
|
- lib/harmoniser/configurable.rb
|
|
47
48
|
- lib/harmoniser/configuration.rb
|
|
48
49
|
- lib/harmoniser/connectable.rb
|
|
49
50
|
- lib/harmoniser/connection.rb
|
|
50
51
|
- lib/harmoniser/definition.rb
|
|
52
|
+
- lib/harmoniser/error_handler.rb
|
|
51
53
|
- lib/harmoniser/launcher.rb
|
|
52
54
|
- lib/harmoniser/launcher/base.rb
|
|
53
55
|
- lib/harmoniser/launcher/bounded.rb
|
|
54
56
|
- lib/harmoniser/launcher/unbounded.rb
|
|
55
57
|
- lib/harmoniser/loggable.rb
|
|
58
|
+
- lib/harmoniser/mock.rb
|
|
59
|
+
- lib/harmoniser/mock/channel.rb
|
|
60
|
+
- lib/harmoniser/mock/connection.rb
|
|
56
61
|
- lib/harmoniser/options.rb
|
|
57
62
|
- lib/harmoniser/parser.rb
|
|
58
63
|
- lib/harmoniser/publisher.rb
|
|
@@ -68,7 +73,7 @@ metadata:
|
|
|
68
73
|
homepage_uri: https://github.com/jollopre/harmoniser
|
|
69
74
|
source_code_uri: https://github.com/jollopre/harmoniser
|
|
70
75
|
changelog_uri: https://github.com/jollopre/harmoniser/blob/master/CHANGELOG.md
|
|
71
|
-
post_install_message:
|
|
76
|
+
post_install_message:
|
|
72
77
|
rdoc_options: []
|
|
73
78
|
require_paths:
|
|
74
79
|
- lib
|
|
@@ -84,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
84
89
|
version: '0'
|
|
85
90
|
requirements: []
|
|
86
91
|
rubygems_version: 3.4.10
|
|
87
|
-
signing_key:
|
|
92
|
+
signing_key:
|
|
88
93
|
specification_version: 4
|
|
89
94
|
summary: A minimalistic approach to interact with RabbitMQ
|
|
90
95
|
test_files: []
|