rabbit_feed 2.1.5 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -5
  3. data/README.md +8 -0
  4. data/example/non_rails_app/Gemfile.lock +4 -6
  5. data/example/non_rails_app/lib/non_rails_app.rb +3 -0
  6. data/example/non_rails_app/spec/spec_helper.rb +1 -0
  7. data/example/rails_app/Gemfile.lock +3 -5
  8. data/example/rails_app/config/initializers/rabbit_feed.rb +3 -0
  9. data/example/rails_app/spec/spec_helper.rb +6 -0
  10. data/lib/rabbit_feed/client.rb +3 -2
  11. data/lib/rabbit_feed/configuration.rb +5 -6
  12. data/lib/rabbit_feed/connection.rb +28 -0
  13. data/lib/rabbit_feed/consumer.rb +3 -1
  14. data/lib/rabbit_feed/consumer_connection.rb +29 -29
  15. data/lib/rabbit_feed/json_log_formatter.rb +12 -0
  16. data/lib/rabbit_feed/producer.rb +3 -1
  17. data/lib/rabbit_feed/producer_connection.rb +12 -30
  18. data/lib/rabbit_feed/testing_support.rb +1 -1
  19. data/lib/rabbit_feed/version.rb +1 -1
  20. data/lib/rabbit_feed.rb +2 -2
  21. data/rabbit_feed.gemspec +1 -3
  22. data/spec/features/connectivity.feature +1 -3
  23. data/spec/features/step_definitions/connectivity_steps.rb +3 -9
  24. data/spec/fixtures/configuration.yml +7 -1
  25. data/spec/lib/rabbit_feed/configuration_spec.rb +2 -6
  26. data/spec/lib/rabbit_feed/consumer_connection_spec.rb +17 -13
  27. data/spec/lib/rabbit_feed/producer_connection_spec.rb +5 -21
  28. data/spec/lib/rabbit_feed/producer_spec.rb +1 -1
  29. data/spec/spec_helper.rb +8 -24
  30. metadata +8 -31
  31. data/lib/rabbit_feed/connection_concern.rb +0 -86
  32. data/spec/lib/rabbit_feed/connection_concern_spec.rb +0 -127
  33. data/spec/support/shared_examples_for_connections.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58c69d09c8fed13331610e7015a5dcbe19e282cd
4
- data.tar.gz: fd29cc489c1bb251ab1e16bf35eda04728cc839e
3
+ metadata.gz: 573abf58e4399420f0328ebfa8d58213ebacd7f2
4
+ data.tar.gz: 4aa80a039f9849ea8058502b96887c46d967b651
5
5
  SHA512:
6
- metadata.gz: 5389a17aa4bffe0bacbf21f170a86fad67dc23349082502e11fff40b5bb404c405d92c6cfad9e2039bf1d1e00f83c0ccac7d12bf72bd02c2b102520199468511
7
- data.tar.gz: 458a801d1631ee4be89aa3a5c59f2fd861e810a93117d536b64b2a1d725f0cc5c4162b5c4648601b81a70c03a1ae341b2926715d4865f1e1dc639ed46ac8831f
6
+ metadata.gz: 942ab068d1c1b6b5c4a0006aeeb8eaf41e19f0d39f2702113701c92bc3fe2e74e23d1172ead1e3085cb41c1c9938b355aec3938d1b9ff3e6d60efecdabb3c917
7
+ data.tar.gz: 74108418645bb5e8aaf5e0a4ca9364834ee2f8b625d0c8cf6237105e3920aa5b65c927e8c14b22590f151e4aa4d1cc6a4f345d23d768b9d7fab681d60a5cab17
data/Gemfile.lock CHANGED
@@ -1,12 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rabbit_feed (2.1.5)
4
+ rabbit_feed (2.3.0)
5
5
  activemodel (>= 3.2.0, < 5.0.0)
6
6
  activesupport (>= 3.2.0, < 5.0.0)
7
7
  avro (>= 1.5.4, < 1.8.0)
8
- bunny (>= 1.1.9, < 1.8.0)
9
- connection_pool (< 2.2.0)
8
+ bunny (= 2.0.0.rc2)
10
9
  pidfile
11
10
 
12
11
  GEM
@@ -28,13 +27,12 @@ GEM
28
27
  debug_inspector (>= 0.0.1)
29
28
  bond (0.5.1)
30
29
  builder (3.2.2)
31
- bunny (1.7.0)
30
+ bunny (2.0.0.rc2)
32
31
  amq-protocol (>= 1.9.2)
33
32
  codeclimate-test-reporter (0.4.6)
34
33
  simplecov (>= 0.7.1, < 1.0.0)
35
34
  coderay (1.1.0)
36
35
  columnize (0.9.0)
37
- connection_pool (2.1.3)
38
36
  debug_inspector (0.0.2)
39
37
  debugger (1.6.8)
40
38
  columnize (>= 0.3.1)
data/README.md CHANGED
@@ -90,6 +90,14 @@ RabbitFeed.instance_eval do
90
90
  end
91
91
  ```
92
92
 
93
+ #### Logging in JSON
94
+
95
+ RabbitFeed log messages are constructed in such a way that they are friendly to JSON log formats. This is done to simplify log aggregation with tools like [Kibana](https://www.elastic.co/products/kibana). To log in JSON format, set the RabbitFeed log to use the JSON formatter, e.g.
96
+
97
+ ```ruby
98
+ RabbitFeed.log.formatter = RabbitFeed::JsonLogFormatter
99
+ ```
100
+
93
101
  ## Producing events
94
102
 
95
103
  The producer defines the events and their payloads using the [Event Definitions DSL](https://github.com/simplybusiness/rabbit_feed#event-definitions-dsl). In a rails app, this can be defined in the [initialiser](https://github.com/simplybusiness/rabbit_feed#initialisation).
@@ -1,12 +1,11 @@
1
1
  PATH
2
2
  remote: ../../
3
3
  specs:
4
- rabbit_feed (2.1.5)
4
+ rabbit_feed (2.3.0)
5
5
  activemodel (>= 3.2.0, < 5.0.0)
6
6
  activesupport (>= 3.2.0, < 5.0.0)
7
7
  avro (>= 1.5.4, < 1.8.0)
8
- bunny (>= 1.1.9, < 1.8.0)
9
- connection_pool (< 2.2.0)
8
+ bunny (= 2.0.0.rc2)
10
9
  pidfile
11
10
 
12
11
  GEM
@@ -25,14 +24,13 @@ GEM
25
24
  avro (1.7.7)
26
25
  multi_json
27
26
  builder (3.2.2)
28
- bunny (1.7.0)
27
+ bunny (2.0.0.rc2)
29
28
  amq-protocol (>= 1.9.2)
30
- connection_pool (2.1.3)
31
29
  diff-lcs (1.2.5)
32
30
  i18n (0.7.0)
33
31
  json (1.8.3)
34
32
  minitest (5.7.0)
35
- multi_json (1.11.1)
33
+ multi_json (1.11.2)
36
34
  pidfile (0.3.0)
37
35
  rake (10.4.2)
38
36
  rspec (3.2.0)
@@ -1,6 +1,9 @@
1
1
  require 'rabbit_feed'
2
2
  require_relative 'non_rails_app/event_handler'
3
3
 
4
+ RabbitFeed.log = Logger.new('log/rabbit_feed.log')
5
+ RabbitFeed.log.formatter = RabbitFeed::JsonLogFormatter
6
+
4
7
  EventRouting do
5
8
  accept_from('rails_app') do
6
9
  event('user_creates_beaver') do |event|
@@ -19,5 +19,6 @@ RSpec.configure do |config|
19
19
  end
20
20
 
21
21
  RabbitFeed.log = Logger.new('log/rabbit_feed.log')
22
+ RabbitFeed.log.formatter = RabbitFeed::JsonLogFormatter
22
23
  RabbitFeed.environment = 'test'
23
24
  RabbitFeed.configuration_file_path = 'config/rabbit_feed.yml'
@@ -1,12 +1,11 @@
1
1
  PATH
2
2
  remote: ../../
3
3
  specs:
4
- rabbit_feed (2.1.5)
4
+ rabbit_feed (2.3.0)
5
5
  activemodel (>= 3.2.0, < 5.0.0)
6
6
  activesupport (>= 3.2.0, < 5.0.0)
7
7
  avro (>= 1.5.4, < 1.8.0)
8
- bunny (>= 1.1.9, < 1.8.0)
9
- connection_pool (< 2.2.0)
8
+ bunny (= 2.0.0.rc2)
10
9
  pidfile
11
10
 
12
11
  GEM
@@ -52,7 +51,7 @@ GEM
52
51
  avro (1.7.7)
53
52
  multi_json
54
53
  builder (3.2.2)
55
- bunny (1.7.0)
54
+ bunny (2.0.0.rc2)
56
55
  amq-protocol (>= 1.9.2)
57
56
  coffee-rails (4.0.1)
58
57
  coffee-script (>= 2.2.0)
@@ -61,7 +60,6 @@ GEM
61
60
  coffee-script-source
62
61
  execjs
63
62
  coffee-script-source (1.9.1)
64
- connection_pool (2.1.3)
65
63
  diff-lcs (1.2.5)
66
64
  erubis (2.7.0)
67
65
  execjs (2.3.0)
@@ -1,3 +1,6 @@
1
+ RabbitFeed.log = Logger.new('log/rabbit_feed.log')
2
+ RabbitFeed.log.formatter = RabbitFeed::JsonLogFormatter
3
+
1
4
  EventDefinitions do
2
5
  define_event('user_creates_beaver', version: '1.0.0') do
3
6
  defined_as do
@@ -1,3 +1,6 @@
1
+ require 'rabbit_feed'
2
+ require 'logger'
3
+
1
4
  RSpec.configure do |config|
2
5
 
3
6
  # Run specs in random order to surface order dependencies. If you find an
@@ -6,3 +9,6 @@ RSpec.configure do |config|
6
9
  # --seed 1234
7
10
  config.order = 'random'
8
11
  end
12
+
13
+ RabbitFeed.log = Logger.new('log/rabbit_feed.log')
14
+ RabbitFeed.log.formatter = RabbitFeed::JsonLogFormatter
@@ -86,8 +86,9 @@ module RabbitFeed
86
86
  end
87
87
 
88
88
  def set_logging
89
- RabbitFeed.log = Logger.new(options[:logfile])
90
- RabbitFeed.log.level = verbose? ? Logger::DEBUG : Logger::INFO
89
+ RabbitFeed.log = Logger.new options[:logfile]
90
+ RabbitFeed.log.level = verbose? ? Logger::DEBUG : Logger::INFO
91
+ RabbitFeed.log.formatter = RabbitFeed::JsonLogFormatter
91
92
  end
92
93
 
93
94
  def set_configuration
@@ -2,19 +2,18 @@ module RabbitFeed
2
2
  class Configuration
3
3
  include ActiveModel::Validations
4
4
 
5
- attr_reader :host, :hosts, :port, :user, :password, :application, :environment, :exchange, :pool_size, :pool_timeout, :heartbeat, :connect_timeout, :network_recovery_interval, :auto_delete_queue, :auto_delete_exchange
6
- validates_presence_of :application, :environment, :exchange, :pool_timeout
5
+ attr_reader :host, :hosts, :port, :user, :password, :application, :environment, :exchange, :heartbeat, :connect_timeout, :network_recovery_interval, :auto_delete_queue, :auto_delete_exchange
6
+ validates_presence_of :application, :environment, :exchange
7
7
 
8
8
  def initialize options
9
- RabbitFeed.log.debug "RabbitFeed initialising with options: #{options}..."
9
+ RabbitFeed.log.info {{ event: :initialize_configuration, options: options.merge({password: :redacted}) }}
10
10
 
11
11
  @host = options[:host]
12
12
  @hosts = options[:hosts]
13
13
  @port = options[:port]
14
14
  @user = options[:user]
15
15
  @password = options[:password]
16
- @exchange = options[:exchange] || 'amq.topic'
17
- @pool_timeout = options[:pool_timeout] || 120
16
+ @exchange = options[:exchange] || 'amq.topic'
18
17
  @heartbeat = options[:heartbeat]
19
18
  @connect_timeout = options[:connect_timeout]
20
19
  @network_recovery_interval = options[:network_recovery_interval]
@@ -26,7 +25,7 @@ module RabbitFeed
26
25
  end
27
26
 
28
27
  def self.load file_path, environment, application
29
- RabbitFeed.log.debug "Reading configurations from #{file_path} in #{environment} for application #{application}..."
28
+ RabbitFeed.log.info {{ event: :load_configuration_file, file_path: file_path, environment: environment, application: application }}
30
29
 
31
30
  raise ConfigurationError.new "The RabbitFeed configuration file path specified does not exist: #{file_path}" unless (File.exist? file_path)
32
31
 
@@ -0,0 +1,28 @@
1
+ module RabbitFeed
2
+ class Connection
3
+ include Singleton
4
+
5
+ def initialize
6
+ RabbitFeed.log.info {{ event: :connecting_to_rabbitmq, options: RabbitFeed.configuration.connection_options.merge({password: :redacted, logger: :redacted}) }}
7
+ @connection = Bunny.new RabbitFeed.configuration.connection_options
8
+ @connection.start
9
+ RabbitFeed.log.info {{ event: :connected_to_rabbitmq }}
10
+ @channel = @connection.create_channel
11
+ @mutex = Mutex.new
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :channel, :mutex
17
+
18
+ def synchronized &block
19
+ mutex.synchronize do
20
+ yield
21
+ end
22
+ end
23
+
24
+ def connection_in_use?
25
+ mutex.locked?
26
+ end
27
+ end
28
+ end
@@ -5,9 +5,11 @@ module RabbitFeed
5
5
  attr_accessor :event_routing
6
6
 
7
7
  def run
8
- ConsumerConnection.consume do |raw_event|
8
+ ConsumerConnection.instance.consume do |raw_event|
9
9
  event = Event.deserialize raw_event
10
+ RabbitFeed.log.info {{ event: :message_received, metadata: event.metadata }}
10
11
  event_routing.handle_event event
12
+ RabbitFeed.log.info {{ event: :message_processed, metadata: event.metadata }}
11
13
  end
12
14
  end
13
15
  end
@@ -1,6 +1,5 @@
1
1
  module RabbitFeed
2
- class ConsumerConnection
3
- include ConnectionConcern
2
+ class ConsumerConnection < RabbitFeed::Connection
4
3
 
5
4
  SUBSCRIPTION_OPTIONS = {
6
5
  consumer_tag: Socket.gethostname, # Use the host name of the server
@@ -19,37 +18,37 @@ module RabbitFeed
19
18
  },
20
19
  }.freeze
21
20
 
22
- attr_reader :queue
23
-
24
- def initialize channel
25
- channel.prefetch(1) # Fetch one message at a time to preserve order
26
- RabbitFeed.log.debug "Declaring queue on #{self.to_s} (channel #{channel.id}) named: #{RabbitFeed.configuration.queue} with options: #{queue_options}..."
21
+ def initialize
22
+ super
23
+ channel.prefetch(1)
27
24
  @queue = channel.queue RabbitFeed.configuration.queue, queue_options
25
+ RabbitFeed.log.info {{ event: :queue_declared, queue: RabbitFeed.configuration.queue, options: queue_options }}
28
26
  bind_on_accepted_routes
29
27
  end
30
28
 
31
- def self.consume &block
32
- with_connection do |consumer_connection|
33
- consumer_connection.consume(&block)
34
- end
35
- end
36
-
37
29
  def consume &block
38
- RabbitFeed.log.info "Consuming messages on #{self.to_s} from queue: #{RabbitFeed.configuration.queue}..."
39
-
40
- consumer = queue.subscribe(SUBSCRIPTION_OPTIONS) do |delivery_info, properties, payload|
41
- handle_message delivery_info, payload, &block
30
+ raise 'This connection already has a consumer subscribed' if connection_in_use?
31
+ synchronized do
32
+ begin
33
+ RabbitFeed.log.info {{ event: :subscribe_to_queue, queue: RabbitFeed.configuration.queue }}
34
+
35
+ consumer = queue.subscribe(SUBSCRIPTION_OPTIONS) do |delivery_info, properties, payload|
36
+ handle_message delivery_info, payload, &block
37
+ end
38
+
39
+ sleep # Sleep indefinitely, as the consumer runs in its own thread
40
+ rescue SystemExit, Interrupt
41
+ RabbitFeed.log.info {{ event: :unsubscribe_from_queue, queue: RabbitFeed.configuration.queue }}
42
+ ensure
43
+ (cancel_consumer consumer) if consumer.present?
44
+ end
42
45
  end
43
-
44
- sleep # Sleep indefinitely, as the consumer runs in its own thread
45
- rescue SystemExit, Interrupt
46
- RabbitFeed.log.info "Consumer #{self.to_s} received exit request, exiting..."
47
- ensure
48
- (cancel_consumer consumer) if consumer.present?
49
46
  end
50
47
 
51
48
  private
52
49
 
50
+ attr_reader :queue
51
+
53
52
  def queue_options
54
53
  {
55
54
  auto_delete: RabbitFeed.configuration.auto_delete_queue,
@@ -60,20 +59,21 @@ module RabbitFeed
60
59
  if RabbitFeed::Consumer.event_routing.present?
61
60
  RabbitFeed::Consumer.event_routing.accepted_routes.each do |accepted_route|
62
61
  queue.bind(RabbitFeed.configuration.exchange, { routing_key: accepted_route })
62
+ RabbitFeed.log.info {{ event: :queue_bound, queue: RabbitFeed.configuration.queue, exchange: RabbitFeed.configuration.exchange, routing_key: accepted_route }}
63
63
  end
64
64
  else
65
65
  queue.bind(RabbitFeed.configuration.exchange)
66
+ RabbitFeed.log.info {{ event: :queue_bound, queue: RabbitFeed.configuration.queue, exchange: RabbitFeed.configuration.exchange }}
66
67
  end
67
68
  end
68
69
 
69
70
  def acknowledge delivery_info
70
71
  queue.channel.ack(delivery_info.delivery_tag)
71
- RabbitFeed.log.debug "Message acknowledged on #{self.to_s} from queue: #{RabbitFeed.configuration.queue}..."
72
+ RabbitFeed.log.debug {{ event: :acknowledge, delivery_tag: delivery_info.delivery_tag }}
72
73
  end
73
74
 
74
75
  def handle_message delivery_info, payload, &block
75
- RabbitFeed.log.debug "Message received on #{self.to_s} from queue: #{RabbitFeed.configuration.queue}..."
76
-
76
+ RabbitFeed.log.debug {{ event: :handling_message, delivery_tag: delivery_info.delivery_tag }}
77
77
  begin
78
78
  yield payload
79
79
  acknowledge delivery_info
@@ -84,19 +84,19 @@ module RabbitFeed
84
84
 
85
85
  def cancel_consumer consumer
86
86
  cancel_ok = consumer.cancel
87
- RabbitFeed.log.debug "Consumer: #{cancel_ok.consumer_tag} cancelled on #{self.to_s} from queue: #{RabbitFeed.configuration.queue}..."
87
+ RabbitFeed.log.debug {{ event: :consumer_cancelled, status: cancel_ok, queue: RabbitFeed.configuration.queue }}
88
88
  end
89
89
 
90
90
  def negative_acknowledge delivery_info
91
91
  # Tell rabbit that we were unable to process the message
92
92
  # This will re-queue the message
93
93
  queue.channel.nack(delivery_info.delivery_tag, false, true)
94
- RabbitFeed.log.debug "Message negatively acknowledged on #{self.to_s} from queue: #{RabbitFeed.configuration.queue}..."
94
+ RabbitFeed.log.debug {{ event: :negative_acknowledge, delivery_tag: delivery_info.delivery_tag }}
95
95
  end
96
96
 
97
97
  def handle_processing_exception delivery_info, exception
98
98
  negative_acknowledge delivery_info
99
- RabbitFeed.log.error "Exception encountered while consuming message on #{self.to_s} from queue #{RabbitFeed.configuration.queue}: #{exception.message} #{exception.backtrace}"
99
+ RabbitFeed.log.error {{ event: :processing_exception, delivery_tag: delivery_info.delivery_tag, message: exception.message, backtrace: exception.backtrace.join(',') }}
100
100
  RabbitFeed.exception_notify exception
101
101
  end
102
102
  end
@@ -0,0 +1,12 @@
1
+ module RabbitFeed
2
+ class JsonLogFormatter < Logger::Formatter
3
+ def self.call(severity, time, progname, msg)
4
+ {
5
+ severity: severity,
6
+ time: time.utc.iso8601(6),
7
+ progname: progname,
8
+ message: msg,
9
+ }.to_json + "\n"
10
+ end
11
+ end
12
+ end
@@ -10,7 +10,9 @@ module RabbitFeed
10
10
  timestamp = Time.now.utc
11
11
  metadata = (metadata event_definition.version, name, timestamp)
12
12
  event = Event.new metadata, payload, event_definition.schema, event_definition.sensitive_fields
13
- ProducerConnection.publish event.serialize, (options name, timestamp)
13
+ RabbitFeed.log.info {{ event: :publish_start, metadata: event.metadata }}
14
+ ProducerConnection.instance.publish event.serialize, (options name, timestamp)
15
+ RabbitFeed.log.info {{ event: :publish_end, metadata: event.metadata }}
14
16
  event
15
17
  end
16
18
 
@@ -1,6 +1,5 @@
1
1
  module RabbitFeed
2
- class ProducerConnection
3
- include ConnectionConcern
2
+ class ProducerConnection < RabbitFeed::Connection
4
3
 
5
4
  PUBLISH_OPTIONS = {
6
5
  persistent: true, # Persist the message to disk
@@ -13,53 +12,36 @@ module RabbitFeed
13
12
  no_declare: false, # Create the exchange if it does not exist
14
13
  }.freeze
15
14
 
16
- attr_reader :exchange
17
-
18
15
  def self.handle_returned_message return_info, content
19
- RabbitFeed.log.error "Handling returned message on #{self.to_s} details: #{return_info}..."
16
+ RabbitFeed.log.error {{ event: :returned_message, return_info: return_info }}
20
17
  RabbitFeed.exception_notify (ReturnedMessageError.new return_info)
21
18
  end
22
19
 
23
- def initialize channel
24
- RabbitFeed.log.debug "Declaring exchange on #{self.to_s} (channel #{channel.id}) named: #{RabbitFeed.configuration.exchange} with options: #{exchange_options}..."
20
+ def initialize
21
+ super
25
22
  @exchange = channel.exchange RabbitFeed.configuration.exchange, exchange_options
26
-
23
+ RabbitFeed.log.info {{ event: :exchange_declared, exchange: RabbitFeed.configuration.exchange, options: exchange_options }}
27
24
  exchange.on_return do |return_info, properties, content|
28
25
  RabbitFeed::ProducerConnection.handle_returned_message return_info, content
29
26
  end
30
27
  end
31
28
 
32
- def self.publish message, options
33
- retry_on_closed_connection do
34
- with_connection do |producer_connection|
35
- retry_on_exception do
36
- producer_connection.publish message, options
37
- end
38
- end
39
- end
40
- end
41
-
42
29
  def publish message, options
43
- # It's critical to dup the options for the sake of retries, as bunny modifies this hash
44
- bunny_options = (options.merge PUBLISH_OPTIONS)
45
-
46
- RabbitFeed.log.debug "Publishing message on #{self.to_s} with options: #{options} to exchange: #{RabbitFeed.configuration.exchange}..."
47
-
48
- exchange.publish message, bunny_options
30
+ synchronized do
31
+ bunny_options = (options.merge PUBLISH_OPTIONS)
32
+ RabbitFeed.log.debug {{ event: :publish, options: options, exchange: RabbitFeed.configuration.exchange }}
33
+ exchange.publish message, bunny_options
34
+ end
49
35
  end
50
36
 
51
37
  private
52
38
 
39
+ attr_reader :exchange
40
+
53
41
  def exchange_options
54
42
  {
55
43
  auto_delete: RabbitFeed.configuration.auto_delete_exchange,
56
44
  }.merge EXCHANGE_OPTIONS
57
45
  end
58
-
59
- def self.connection_options
60
- super.merge({
61
- threaded: false, # With threading enabled, there is a chance of losing an event during connection recovery
62
- })
63
- end
64
46
  end
65
47
  end
@@ -18,7 +18,7 @@ module RabbitFeed
18
18
 
19
19
  TestingSupport.published_events = []
20
20
 
21
- allow(RabbitFeed::ProducerConnection).to receive(:publish) do |serialized_event, routing_key|
21
+ allow(RabbitFeed::ProducerConnection.instance).to receive(:publish) do |serialized_event, routing_key|
22
22
  TestingSupport.published_events << (Event.deserialize serialized_event)
23
23
  end
24
24
  end
@@ -1,3 +1,3 @@
1
1
  module RabbitFeed
2
- VERSION = '2.1.5'
2
+ VERSION = '2.3.0'
3
3
  end
data/lib/rabbit_feed.rb CHANGED
@@ -2,14 +2,13 @@ require 'active_support/all'
2
2
  require 'active_model'
3
3
  require 'avro'
4
4
  require 'bunny'
5
- require 'connection_pool'
6
5
  require 'yaml'
7
6
  require_relative './dsl'
8
7
  require 'rabbit_feed/version'
9
8
  require 'rabbit_feed/client'
10
9
  require 'rabbit_feed/configuration'
11
- require 'rabbit_feed/connection_concern'
12
10
  require 'rabbit_feed/event'
11
+ require 'rabbit_feed/connection'
13
12
  require 'rabbit_feed/consumer_connection'
14
13
  require 'rabbit_feed/consumer'
15
14
  require 'rabbit_feed/event_routing'
@@ -18,6 +17,7 @@ require 'rabbit_feed/producer'
18
17
  require 'rabbit_feed/event_definitions'
19
18
  require 'rabbit_feed/testing_support'
20
19
  require 'rabbit_feed/version'
20
+ require 'rabbit_feed/json_log_formatter'
21
21
 
22
22
  module RabbitFeed
23
23
  extend self
data/rabbit_feed.gemspec CHANGED
@@ -19,13 +19,11 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  # Gem for interfacing with RabbitMq
22
- spec.add_dependency 'bunny', '>= 1.1.9', '< 1.8.0'
22
+ spec.add_dependency 'bunny', '2.0.0.rc2'
23
23
  # We use some helpers from ActiveSupport
24
24
  spec.add_dependency 'activesupport', '>= 3.2.0', '< 5.0.0'
25
25
  # We use validations from ActiveModel
26
26
  spec.add_dependency 'activemodel', '>= 3.2.0', '< 5.0.0'
27
- # Provides connection pooling for the producer connections
28
- spec.add_dependency 'connection_pool', '< 2.2.0'
29
27
  # Manages process pidfile
30
28
  spec.add_dependency 'pidfile'
31
29
  # Schema definitions and serialization for events
@@ -1,3 +1,4 @@
1
+ @connectivity
1
2
  Feature:
2
3
  As a developer
3
4
  I want to be able to publish and consume events
@@ -6,8 +7,5 @@ Scenario: I can publish and consume events
6
7
  Given I am consuming
7
8
  When I publish an event
8
9
  Then I receive that event
9
-
10
- Scenario: When an event cannot be consumed it remains on the queue
11
- Given I am consuming
12
10
  When I publish an event that cannot be processed by the consumer
13
11
  Then the event remains on the queue
@@ -2,12 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  step 'I am consuming' do
4
4
  set_event_routing
5
- initialize_queue
5
+ set_event_definitions
6
+ RabbitFeed::ConsumerConnection.instance # Bind the queue
6
7
  @consumer_thread = Thread.new{ RabbitFeed::Consumer.run }
7
8
  end
8
9
 
9
10
  step 'I publish an event' do
10
- set_event_definitions
11
11
  publish 'test'
12
12
  end
13
13
 
@@ -17,7 +17,6 @@ step 'I receive that event' do
17
17
  end
18
18
 
19
19
  step 'I publish an event that cannot be processed by the consumer' do
20
- set_event_definitions
21
20
  publish 'test_failure'
22
21
  end
23
22
 
@@ -29,11 +28,6 @@ end
29
28
 
30
29
  module Turnip::Steps
31
30
 
32
- def initialize_queue
33
- RabbitFeed::ProducerConnection.with_connection{|connection|}
34
- RabbitFeed::ConsumerConnection.with_connection{|connection|}
35
- end
36
-
37
31
  def publish event_name
38
32
  @event_text = "#{event_name}_#{Time.now.iso8601(6)}"
39
33
  RabbitFeed::Producer.publish_event event_name, { 'field' => @event_text }
@@ -46,7 +40,7 @@ module Turnip::Steps
46
40
 
47
41
  def wait_for_event
48
42
  begin
49
- Timeout::timeout(5.0) do
43
+ Timeout::timeout(2.0) do
50
44
  until @consumed_events.any? do
51
45
  sleep 0.1
52
46
  end
@@ -1,11 +1,17 @@
1
1
  test:
2
+ host: localhost
3
+ port: 5672
4
+ user: guest
5
+ password: guest
6
+ application: rabbit_feed
7
+ auto_delete_queue: true
8
+ test_config:
2
9
  host: localhost
3
10
  port: 5672
4
11
  user: guest
5
12
  password: guest
6
13
  application: rabbit_feed
7
14
  exchange: rabbit_feed_exchange
8
- pool_timeout: 1
9
15
  heartbeat: 60
10
16
  connect_timeout: 1
11
17
  network_recovery_interval: 0.1
@@ -68,7 +68,7 @@ module RabbitFeed
68
68
 
69
69
  describe '.load' do
70
70
  let(:file_path) { 'spec/fixtures/configuration.yml' }
71
- let(:environment) { 'test' }
71
+ let(:environment) { 'test_config' }
72
72
  let(:application) { }
73
73
  subject { described_class.load file_path, environment, application }
74
74
 
@@ -95,9 +95,8 @@ module RabbitFeed
95
95
  its(:user) { should eq 'guest' }
96
96
  its(:password) { should eq 'guest' }
97
97
  its(:application) { should eq 'rabbit_feed' }
98
- its(:environment) { should eq 'test' }
98
+ its(:environment) { should eq 'test_config' }
99
99
  its(:exchange) { should eq 'rabbit_feed_exchange' }
100
- its(:pool_timeout) { should eq 1 }
101
100
  its(:heartbeat) { should eq 60 }
102
101
  its(:connect_timeout) { should eq 1 }
103
102
  its(:network_recovery_interval) { should eq 0.1 }
@@ -142,7 +141,6 @@ module RabbitFeed
142
141
  its(:heartbeat) { should be_nil }
143
142
  its(:network_recovery_interval) { should be_nil }
144
143
  its(:exchange) { should eq 'amq.topic' }
145
- its(:pool_timeout) { should eq 120 }
146
144
  its(:connect_timeout) { should be_nil }
147
145
  its(:auto_delete_queue) { should be_falsey }
148
146
  its(:auto_delete_exchange) { should be_falsey }
@@ -159,7 +157,6 @@ module RabbitFeed
159
157
  application: 'rabbit_feed',
160
158
  environment: 'test',
161
159
  exchange: 'exchange_name',
162
- pool_timeout: 6,
163
160
  heartbeat: 3,
164
161
  connect_timeout: 4,
165
162
  network_recovery_interval: 2,
@@ -176,7 +173,6 @@ module RabbitFeed
176
173
  its(:application) { should eq 'rabbit_feed' }
177
174
  its(:environment) { should eq 'test' }
178
175
  its(:exchange) { should eq 'exchange_name' }
179
- its(:pool_timeout) { should eq 6 }
180
176
  its(:heartbeat) { should eq 3 }
181
177
  its(:connect_timeout) { should eq 4 }
182
178
  its(:network_recovery_interval) { should eq 2 }
@@ -9,7 +9,9 @@ module RabbitFeed
9
9
  allow(Bunny).to receive(:new).and_return(bunny_connection)
10
10
  allow(bunny_queue).to receive(:channel).and_return(bunny_channel)
11
11
  end
12
- subject{ described_class.new bunny_channel }
12
+ subject do
13
+ Class.new(described_class).instance
14
+ end
13
15
 
14
16
  describe '#new' do
15
17
  before do
@@ -21,27 +23,16 @@ module RabbitFeed
21
23
  end
22
24
 
23
25
  it 'binds the queue to the exchange' do
24
- expect(bunny_queue).to receive(:bind).with('rabbit_feed_exchange', { routing_key: 'test.rabbit_feed.test'})
26
+ expect(bunny_queue).to receive(:bind).with('amq.topic', { routing_key: 'test.rabbit_feed.test'})
25
27
  subject
26
28
  end
27
29
 
28
- it 'assigns the queue' do
29
- expect(subject.queue).to eq bunny_queue
30
- end
31
-
32
30
  it 'preserves message order' do
33
31
  expect(bunny_channel).to receive(:prefetch).with(1)
34
32
  subject
35
33
  end
36
34
  end
37
35
 
38
- describe '#connection_options' do
39
-
40
- it 'uses a threaded connection' do
41
- expect(described_class.connection_options).to include(threaded: true)
42
- end
43
- end
44
-
45
36
  describe '#consume' do
46
37
  before do
47
38
  allow(bunny_queue).to receive(:subscribe).and_yield(double(:delivery_info, delivery_tag: :tag), 'properties', 'payload')
@@ -58,11 +49,24 @@ module RabbitFeed
58
49
  subject.consume { }
59
50
  end
60
51
 
52
+ it 'is synchronized' do
53
+ expect(subject).to receive(:synchronized).and_call_original
54
+ subject.consume { }
55
+ end
56
+
61
57
  it 'cancels the consumer' do
62
58
  expect_any_instance_of(described_class).to receive(:cancel_consumer)
63
59
  subject.consume { }
64
60
  end
65
61
 
62
+ context 'when consuming' do
63
+ before { allow(subject.send(:mutex)).to receive(:locked?).and_return(true) }
64
+
65
+ it 'raises when attempting to consume in parallel' do
66
+ expect{ subject.consume { } }.to raise_error 'This connection already has a consumer subscribed'
67
+ end
68
+ end
69
+
66
70
  context 'when an exception is raised' do
67
71
 
68
72
  context 'when Airbrake is defined' do
@@ -9,7 +9,7 @@ module RabbitFeed
9
9
  allow(Bunny).to receive(:new).and_return(bunny_connection)
10
10
  end
11
11
  subject do
12
- described_class.new bunny_channel
12
+ Class.new(described_class).instance
13
13
  end
14
14
 
15
15
  describe '#new' do
@@ -18,10 +18,6 @@ module RabbitFeed
18
18
  expect(bunny_exchange).to receive(:on_return).and_yield('return_info', 'properties', 'content')
19
19
  subject
20
20
  end
21
-
22
- it 'assigns the exchange' do
23
- expect(subject.exchange).to eq bunny_exchange
24
- end
25
21
  end
26
22
 
27
23
  describe '#handle_returned_message' do
@@ -42,30 +38,18 @@ module RabbitFeed
42
38
  end
43
39
  end
44
40
 
45
- describe '#connection_options' do
46
-
47
- it 'does not use a threaded connection' do
48
- expect(described_class.connection_options).to include(threaded: false)
49
- end
50
- end
51
-
52
41
  describe '#publish' do
53
42
  let(:message) { 'the message' }
54
43
  let(:options) { {routing_key: 'routing_key'} }
55
44
 
56
45
  it 'publishes the message as mandatory and persistent' do
57
46
  expect(bunny_exchange).to receive(:publish).with(message, { persistent: true, mandatory: true, routing_key: 'routing_key' })
58
- described_class.publish message, options
59
- end
60
-
61
- it 'retries on closed connections' do
62
- expect(described_class).to receive(:retry_on_closed_connection).and_call_original
63
- described_class.publish message, options
47
+ subject.publish message, options
64
48
  end
65
49
 
66
- it 'retries on exception' do
67
- expect(described_class).to receive(:retry_on_exception).twice.and_call_original
68
- described_class.publish message, options
50
+ it 'is synchronized' do
51
+ expect(subject).to receive(:synchronized).and_call_original
52
+ subject.publish message, options
69
53
  end
70
54
  end
71
55
  end
@@ -59,7 +59,7 @@ module RabbitFeed
59
59
 
60
60
  it 'serializes the event and provides message metadata' do
61
61
  Timecop.freeze do
62
- expect(ProducerConnection).to receive(:publish).with(
62
+ expect(ProducerConnection.instance).to receive(:publish).with(
63
63
  an_instance_of(String),
64
64
  {
65
65
  routing_key: 'test.rabbit_feed.event_name',
data/spec/spec_helper.rb CHANGED
@@ -30,34 +30,18 @@ RSpec.configure do |config|
30
30
  reset_environment
31
31
  end
32
32
 
33
- config.after do
34
- reset_environment
35
- # Ensure the consumer thread exists between tests
36
- kill_consumer_thread
37
- # Ensure that connections don't persist between tests
38
- close_connections
39
- # Clear event routing
40
- RabbitFeed::Consumer.event_routing = nil
41
- # Clear event definitions
42
- RabbitFeed::Producer.event_definitions = nil
33
+ config.after(connectivity: true) do
34
+ Thread.kill @consumer_thread if @consumer_thread.present?
43
35
  end
44
36
 
45
37
  RabbitFeed::TestingSupport.include_support config
46
38
  end
47
39
 
48
- def kill_consumer_thread
49
- if @consumer_thread.present?
50
- Thread.kill @consumer_thread
51
- end
52
- end
53
-
54
- def close_connections
55
- RabbitFeed::ProducerConnection.close
56
- RabbitFeed::ConsumerConnection.close
57
- end
58
-
59
40
  def reset_environment
60
- RabbitFeed.log = Logger.new('test.log')
61
- RabbitFeed.environment = 'test'
62
- RabbitFeed.configuration_file_path = 'spec/fixtures/configuration.yml'
41
+ RabbitFeed.log = Logger.new('test.log')
42
+ RabbitFeed.log.formatter = RabbitFeed::JsonLogFormatter
43
+ RabbitFeed.environment = 'test'
44
+ RabbitFeed.configuration_file_path = 'spec/fixtures/configuration.yml'
45
+ RabbitFeed::Consumer.event_routing = nil
46
+ RabbitFeed::Producer.event_definitions = nil
63
47
  end
metadata CHANGED
@@ -1,35 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabbit_feed
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.5
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simply Business
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-09 00:00:00.000000000 Z
11
+ date: 2015-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.1.9
20
- - - "<"
17
+ - - '='
21
18
  - !ruby/object:Gem::Version
22
- version: 1.8.0
19
+ version: 2.0.0.rc2
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - ">="
24
+ - - '='
28
25
  - !ruby/object:Gem::Version
29
- version: 1.1.9
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: 1.8.0
26
+ version: 2.0.0.rc2
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: activesupport
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -70,20 +64,6 @@ dependencies:
70
64
  - - "<"
71
65
  - !ruby/object:Gem::Version
72
66
  version: 5.0.0
73
- - !ruby/object:Gem::Dependency
74
- name: connection_pool
75
- requirement: !ruby/object:Gem::Requirement
76
- requirements:
77
- - - "<"
78
- - !ruby/object:Gem::Version
79
- version: 2.2.0
80
- type: :runtime
81
- prerelease: false
82
- version_requirements: !ruby/object:Gem::Requirement
83
- requirements:
84
- - - "<"
85
- - !ruby/object:Gem::Version
86
- version: 2.2.0
87
67
  - !ruby/object:Gem::Dependency
88
68
  name: pidfile
89
69
  requirement: !ruby/object:Gem::Requirement
@@ -258,12 +238,13 @@ files:
258
238
  - lib/rabbit_feed.rb
259
239
  - lib/rabbit_feed/client.rb
260
240
  - lib/rabbit_feed/configuration.rb
261
- - lib/rabbit_feed/connection_concern.rb
241
+ - lib/rabbit_feed/connection.rb
262
242
  - lib/rabbit_feed/consumer.rb
263
243
  - lib/rabbit_feed/consumer_connection.rb
264
244
  - lib/rabbit_feed/event.rb
265
245
  - lib/rabbit_feed/event_definitions.rb
266
246
  - lib/rabbit_feed/event_routing.rb
247
+ - lib/rabbit_feed/json_log_formatter.rb
267
248
  - lib/rabbit_feed/producer.rb
268
249
  - lib/rabbit_feed/producer_connection.rb
269
250
  - lib/rabbit_feed/testing_support.rb
@@ -281,7 +262,6 @@ files:
281
262
  - spec/fixtures/configuration.yml
282
263
  - spec/lib/rabbit_feed/client_spec.rb
283
264
  - spec/lib/rabbit_feed/configuration_spec.rb
284
- - spec/lib/rabbit_feed/connection_concern_spec.rb
285
265
  - spec/lib/rabbit_feed/consumer_connection_spec.rb
286
266
  - spec/lib/rabbit_feed/event_definitions_spec.rb
287
267
  - spec/lib/rabbit_feed/event_routing_spec.rb
@@ -291,7 +271,6 @@ files:
291
271
  - spec/lib/rabbit_feed/testing_support/rspec_matchers/publish_event_spec.rb
292
272
  - spec/lib/rabbit_feed/testing_support/testing_helper_spec.rb
293
273
  - spec/spec_helper.rb
294
- - spec/support/shared_examples_for_connections.rb
295
274
  homepage: https://github.com/simplybusiness/rabbit_feed
296
275
  licenses:
297
276
  - MIT
@@ -323,7 +302,6 @@ test_files:
323
302
  - spec/fixtures/configuration.yml
324
303
  - spec/lib/rabbit_feed/client_spec.rb
325
304
  - spec/lib/rabbit_feed/configuration_spec.rb
326
- - spec/lib/rabbit_feed/connection_concern_spec.rb
327
305
  - spec/lib/rabbit_feed/consumer_connection_spec.rb
328
306
  - spec/lib/rabbit_feed/event_definitions_spec.rb
329
307
  - spec/lib/rabbit_feed/event_routing_spec.rb
@@ -333,5 +311,4 @@ test_files:
333
311
  - spec/lib/rabbit_feed/testing_support/rspec_matchers/publish_event_spec.rb
334
312
  - spec/lib/rabbit_feed/testing_support/testing_helper_spec.rb
335
313
  - spec/spec_helper.rb
336
- - spec/support/shared_examples_for_connections.rb
337
314
  has_rdoc:
@@ -1,86 +0,0 @@
1
- module RabbitFeed
2
- module ConnectionConcern
3
- extend ActiveSupport::Concern
4
-
5
- module ClassMethods
6
-
7
- def connection_options
8
- RabbitFeed.configuration.connection_options
9
- end
10
-
11
- def with_connection &block
12
- connection_pool.with do |connection|
13
- yield connection
14
- end
15
- end
16
-
17
- def retry_on_exception tries=3, &block
18
- yield
19
- rescue Bunny::ConnectionClosedError
20
- raise # There is no point in retrying if the connection is closed
21
- rescue => e
22
- RabbitFeed.log.warn "Exception encountered; #{tries - 1} tries remaining. #{self.to_s}: #{e.message} #{e.backtrace}"
23
- unless (tries -= 1).zero?
24
- retry
25
- end
26
- raise
27
- end
28
-
29
- def retry_on_closed_connection tries=3, &block
30
- yield
31
- rescue Bunny::ConnectionClosedError => e
32
- RabbitFeed.log.warn "Closed connection exception encountered; #{tries - 1} tries remaining. #{self.to_s}: #{e.message} #{e.backtrace}"
33
- unless (tries -= 1).zero?
34
- unset_connection
35
- sleep 1
36
- retry
37
- end
38
- raise
39
- end
40
-
41
- def close
42
- RabbitFeed.log.debug "Closing connection: #{self.to_s}..."
43
- @bunny_connection.close if @bunny_connection.present? && !closed?
44
- unset_connection
45
- rescue => e
46
- RabbitFeed.log.warn "Exception encountered whilst closing #{self.to_s}: #{e.message} #{e.backtrace}"
47
- end
48
-
49
- def bunny_connection
50
- if @bunny_connection.nil?
51
- retry_on_exception do
52
- RabbitFeed.log.debug "Opening connection: #{self.to_s}..."
53
- @bunny_connection = Bunny.new connection_options
54
- @bunny_connection.start
55
- end
56
- end
57
-
58
- @bunny_connection
59
- end
60
- private :bunny_connection
61
-
62
- def connection_pool
63
- @connection_pool ||= ConnectionPool.new(
64
- size: 1,
65
- timeout: RabbitFeed.configuration.pool_timeout
66
- ) do
67
- new bunny_connection.create_channel
68
- end
69
- end
70
- private :connection_pool
71
-
72
- def closed?
73
- @bunny_connection.present? && @bunny_connection.closed?
74
- end
75
- private :closed?
76
-
77
- def unset_connection
78
- RabbitFeed.log.debug "Unsetting connection: #{self.to_s}..."
79
- @connection_pool = nil
80
- @bunny_connection = nil
81
- end
82
- private :unset_connection
83
-
84
- end
85
- end
86
- end
@@ -1,127 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module RabbitFeed
4
- describe ConnectionConcern do
5
- let(:bunny_exchange) { double(:bunny_exchange, on_return: nil) }
6
- let(:connection_closed) { false }
7
- let(:bunny_channel) { double(:bunny_channel, exchange: bunny_exchange, id: 1) }
8
- let(:bunny_connection) { double(:bunny_connection, start: nil, closed?: connection_closed, close: nil, create_channel: bunny_channel) }
9
- before { allow(Bunny).to receive(:new).and_return(bunny_connection) }
10
- after do
11
- subject.instance_variable_set(:@bunny_connection, nil)
12
- subject.instance_variable_set(:@connection_pool, nil)
13
- end
14
- subject { RabbitFeed::ProducerConnection }
15
-
16
- describe '.with_connection' do
17
-
18
- it 'retries on exception' do
19
- expect(subject).to receive(:retry_on_exception).and_call_original
20
- subject.with_connection{|connection| connection }
21
- end
22
-
23
- it 'assigns the connection' do
24
- subject.with_connection{|connection| connection }
25
- expect(subject.instance_variable_get(:@bunny_connection)).to eq bunny_connection
26
- end
27
-
28
- it 'assigns the connection pool' do
29
- subject.with_connection{|connection| connection }
30
- expect(subject.instance_variable_get(:@connection_pool)).to be_a ConnectionPool
31
- end
32
-
33
- it 'creates a connection pool of one connection' do
34
- expect(ConnectionPool).to receive(:new).with(hash_including({size: 1})).and_call_original
35
- subject.with_connection{|connection| connection }
36
- end
37
-
38
- it 'provides an instance of the class' do
39
- actual = subject.with_connection{|connection| connection }
40
- expect(actual).to be_a subject
41
- end
42
- end
43
-
44
- describe '.close' do
45
-
46
- context 'when the connection is nil' do
47
-
48
- it 'does not close the connection' do
49
- expect(bunny_connection).not_to receive(:close)
50
- subject.close
51
- end
52
- end
53
-
54
- context 'when the connection is not nil' do
55
- before do
56
- subject.with_connection{|connection| connection }
57
- end
58
-
59
- context 'when the connection is closed' do
60
- let(:connection_closed) { true }
61
-
62
- it 'does not close the connection' do
63
- expect(bunny_connection).not_to receive(:close)
64
- subject.close
65
- end
66
- end
67
-
68
- context 'when the connection is not closed' do
69
- let(:connection_closed) { false }
70
-
71
- it 'closes the connection' do
72
- expect(bunny_connection).to receive(:close)
73
- subject.close
74
- end
75
-
76
- context 'when closing raises an exception' do
77
-
78
- it 'does not propogate the exception' do
79
- allow(bunny_connection).to receive(:close).and_raise 'error'
80
- expect{ subject.close }.not_to raise_error
81
- end
82
- end
83
- end
84
-
85
- it 'unsets the connection' do
86
- subject.close
87
- expect(subject.instance_variable_get(:@bunny_connection)).to be_nil
88
- end
89
-
90
- it 'unsets the connection pool' do
91
- subject.close
92
- expect(subject.instance_variable_get(:@connection_pool)).to be_nil
93
- end
94
- end
95
- end
96
-
97
- describe '.retry_on_exception' do
98
- it_behaves_like 'an operation that retries on exception', :retry_on_exception, RuntimeError
99
- it_behaves_like 'an operation that does not retry on exception', :retry_on_exception, Bunny::ConnectionClosedError
100
- end
101
-
102
- describe '.retry_on_closed_connection' do
103
- before do
104
- subject.with_connection{|connection| connection }
105
- allow(subject).to receive(:sleep).at_least(:once)
106
- end
107
-
108
- it_behaves_like 'an operation that retries on exception', :retry_on_closed_connection, Bunny::ConnectionClosedError
109
- it_behaves_like 'an operation that does not retry on exception', :retry_on_closed_connection, RuntimeError
110
-
111
- it 'unsets the connection' do
112
- expect { subject.retry_on_closed_connection { raise Bunny::ConnectionClosedError.new 'blah' } }.to raise_error
113
- expect(subject.instance_variable_get(:@bunny_connection)).to be_nil
114
- end
115
-
116
- it 'unsets the connection pool' do
117
- expect { subject.retry_on_closed_connection { raise Bunny::ConnectionClosedError.new 'blah' } }.to raise_error
118
- expect(subject.instance_variable_get(:@connection_pool)).to be_nil
119
- end
120
-
121
- it 'waits between retries' do
122
- expect(subject).to receive(:sleep).with(1).twice
123
- begin; subject.retry_on_closed_connection { raise Bunny::ConnectionClosedError.new 'blah' }; rescue; end
124
- end
125
- end
126
- end
127
- end
@@ -1,40 +0,0 @@
1
- RSpec.shared_examples 'an operation that retries on exception' do |operation, exception_class|
2
-
3
- context 'less than three times' do
4
-
5
- it 'traps the exception' do
6
- tries = 0
7
- expect do
8
- subject.send(operation) do
9
- (tries += 1) < 3 ? (raise exception_class.new 'blah') : nil
10
- end
11
- end.to_not raise_error
12
- end
13
- end
14
-
15
- context 'three or more times' do
16
-
17
- it 'raises the exception' do
18
- tries = 0
19
- expect do
20
- subject.send(operation) do
21
- tries += 1
22
- raise exception_class.new 'blah'
23
- end
24
- end.to raise_error exception_class
25
- expect(tries).to eq 3
26
- end
27
- end
28
- end
29
-
30
- RSpec.shared_examples 'an operation that does not retry on exception' do |operation, exception_class|
31
-
32
- it 'does not retry when the error is received' do
33
- tries = 0
34
- expect do
35
- subject.send(operation) do
36
- (tries += 1) < 3 ? (raise exception_class.new 'blah') : nil
37
- end
38
- end.to raise_error
39
- end
40
- end