songkick_queue 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75d2f489f41e935f1c16ef27861a7d9044472eb5
4
- data.tar.gz: d154004c62f76f7a99cff3841fbccaf863930c8e
3
+ metadata.gz: 8b51761dcb720a861e59cd35370dd964fbb0393e
4
+ data.tar.gz: fdcf57c99fa12a9e9e3dbfc9a4c83b8a765486ce
5
5
  SHA512:
6
- metadata.gz: 815d1add90cd8a2d5adc0922babfb3de115d098ee0d86f45d77fc6c92b1e996da766c8ecb244cd22939976581dcb70490d493cec53b433b18dbd6d115334570d
7
- data.tar.gz: 19f0a0f9f4a7dc0f145df5b21f6a96e0fa5ddf1c8e1194fb6810a40878f7e990847fcd3220e5d097d74d6fb3ca7ce37ca4e45281ca9bdd67ef76f12b592e32a6
6
+ metadata.gz: 78d96fe086dfe7afc88548bdfcae4237ecc8fccc95d6d28d24e643b21c058cfd3eb99b8bf49c4e20bff89770763257ace02c56dc672c7d8c6c900f3ea519fabc
7
+ data.tar.gz: feb0f64206f09160396154611dd732c8327b86f146d82afae897719ae90e097cb9634d008999b17dc3985eef57be9596c0190d7bc979a9467c8d169e9dcd7251
data/README.md CHANGED
@@ -40,6 +40,8 @@ SongkickQueue.configure do |config|
40
40
  config.username = 'guest'
41
41
  config.password = 'guest'
42
42
  config.vhost = '/'
43
+ config.max_reconnect_attempts = 10
44
+ config.network_recovery_interval = 1.0
43
45
  end
44
46
  ```
45
47
 
@@ -1,7 +1,7 @@
1
- # Example environment file
1
+ # Example consumer
2
2
  # Require this file when running `songkick_queue` like so:
3
3
  #
4
- # $ bin/songkick_queue --require ./examples/environment.rb --consumer TweetConsumer
4
+ # $ bin/songkick_queue --require ./examples/consumer.rb --consumer TweetConsumer
5
5
  #
6
6
  require_relative '../lib/songkick_queue'
7
7
 
@@ -1,3 +1,8 @@
1
+ # Example producer
2
+ # Run this file like so:
3
+ #
4
+ # $ bundle exec ruby examples/producer.rb
5
+ #
1
6
  require_relative '../lib/songkick_queue'
2
7
 
3
8
  SongkickQueue.configure do |config|
@@ -18,18 +18,22 @@ module SongkickQueue
18
18
  :username,
19
19
  :password,
20
20
  :vhost,
21
+ :max_reconnect_attempts,
22
+ :network_recovery_interval,
21
23
  )
22
24
 
23
- ConfigurationError = Class.new(StandardError)
25
+ TooManyReconnectAttemptsError = Class.new(StandardError)
24
26
 
25
27
  # Retrieve configuration for SongkickQueue
26
28
  #
27
29
  # @return [Configuration]
28
30
  def self.configuration
29
- @configuration ||= Configuration.new(
30
- logger: Logger.new(STDOUT),
31
- port: 5672,
32
- )
31
+ @configuration ||= Configuration.new.tap do |config|
32
+ config.logger = Logger.new(STDOUT)
33
+ config.port = 5672
34
+ config.max_reconnect_attempts = 10
35
+ config.network_recovery_interval = 1.0
36
+ end
33
37
  end
34
38
 
35
39
  # Yields a block, passing the memoized configuration instance
@@ -8,36 +8,43 @@ module SongkickQueue
8
8
  #
9
9
  # @return [Bunny::Channel]
10
10
  def channel
11
- @channel ||= begin
12
- channel = connection.create_channel
13
- channel.prefetch(1)
14
-
15
- channel
16
- end
11
+ @channel ||= build_channel
17
12
  end
18
13
 
19
14
  # Creates a memoized connection to RabbitMQ
20
15
  #
21
16
  # @return [Bunny::Session]
22
17
  def connection
23
- @connection ||= begin
24
- connection = Bunny.new(
25
- host: config.host,
26
- port: config.port,
27
- username: config.username,
28
- password: config.password,
29
- vhost: config.vhost,
30
- heartbeat_interval: 60,
31
- )
32
-
33
- connection.start
34
-
35
- connection
36
- end
18
+ @connection ||= build_connection
37
19
  end
38
20
 
39
21
  private
40
22
 
23
+ def build_channel
24
+ channel = connection.create_channel
25
+ channel.prefetch(1)
26
+
27
+ channel
28
+ end
29
+
30
+ def build_connection
31
+ connection = Bunny.new(
32
+ host: config.host,
33
+ port: config.port,
34
+ username: config.username,
35
+ password: config.password,
36
+ vhost: config.vhost,
37
+ heartbeat_interval: 10,
38
+ automatically_recover: true,
39
+ network_recovery_interval: config.network_recovery_interval,
40
+ recover_from_connection_close: true,
41
+ )
42
+
43
+ connection.start
44
+
45
+ connection
46
+ end
47
+
41
48
  def config
42
49
  SongkickQueue.configuration
43
50
  end
@@ -1,16 +1,23 @@
1
1
  module SongkickQueue
2
2
  class Producer
3
+ attr_accessor :reconnect_attempts
4
+
3
5
  def initialize
4
6
  @client = Client.new
7
+ @reconnect_attempts = 0
8
+ @publish_reconnect_delay = 5.0
5
9
  end
6
10
 
7
- # Serializes the given message and publishes it to the default RabbitMQ
8
- # exchange
11
+ # Serializes the given message and publishes it to the default RabbitMQ exchange
9
12
  #
10
13
  # @param queue_name [String] to publish to
11
14
  # @param message [#to_json] to serialize and enqueue
12
15
  # @option options [String] :message_id to pass through to the consumer (will be logged)
13
16
  # @option options [String] :produced_at time when the message was created, ISO8601 formatted
17
+ #
18
+ # @raise [TooManyReconnectAttemptsError] if max reconnect attempts is exceeded
19
+ #
20
+ # @return [Bunny::Exchange]
14
21
  def publish(queue_name, payload, options = {})
15
22
  message_id = options.fetch(:message_id) { SecureRandom.hex(6) }
16
23
  produced_at = options.fetch(:produced_at) { Time.now.utc.iso8601 }
@@ -23,26 +30,55 @@ module SongkickQueue
23
30
 
24
31
  message = JSON.generate(message)
25
32
 
26
- client
33
+ exchange = client
27
34
  .default_exchange
28
35
  .publish(message, routing_key: String(queue_name))
29
36
 
30
37
  logger.info "Published message #{message_id} to '#{queue_name}' at #{produced_at}"
38
+
39
+ exchange
40
+ rescue Bunny::ConnectionClosedError
41
+ self.reconnect_attempts += 1
42
+
43
+ if (reconnect_attempts > config.max_reconnect_attempts)
44
+ fail TooManyReconnectAttemptsError, "Attempted to reconnect more than " +
45
+ "#{config.max_reconnect_attempts} times"
46
+ end
47
+
48
+ logger.info "Attempting to reconnect to RabbitMQ, attempt #{reconnect_attempts} " +
49
+ "of #{config.max_reconnect_attempts}"
50
+
51
+ wait_for_bunny_session_to_reconnect
52
+
53
+ retry
31
54
  end
32
55
 
33
56
  private
34
57
 
35
- # Retrieve the logger defined in the configuration
58
+ # When retrying publishing of a message after a ConnectionClosedError, we must first wait for
59
+ # the defined network_recovery_interval and then a bit longer for it to reopen connections and
60
+ # channels etc...
61
+ #
62
+ # If we attempt to publish again before the connection has been reopened we'll catch the
63
+ # Bunny::ConnectionClosedError exception again and just use another attempt to try and connect.
36
64
  #
37
- # @raise [ConfigurationError] if not defined
65
+ # @todo Optimize this to know when the connection is open again, rather than picking an
66
+ # arbitary time period.
67
+ #
68
+ # @return [void]
69
+ def wait_for_bunny_session_to_reconnect
70
+ wait_time = config.network_recovery_interval + publish_reconnect_delay
71
+ sleep wait_time
72
+ end
73
+
38
74
  def logger
39
- config.logger || fail(ConfigurationError, 'No logger configured, see README for more details')
75
+ config.logger
40
76
  end
41
77
 
42
78
  def config
43
79
  SongkickQueue.configuration
44
80
  end
45
81
 
46
- attr_reader :client
82
+ attr_reader :client, :publish_reconnect_delay
47
83
  end
48
84
  end
@@ -1,3 +1,3 @@
1
1
  module SongkickQueue
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -103,11 +103,8 @@ module SongkickQueue
103
103
  client.channel
104
104
  end
105
105
 
106
- # Retrieve the logger defined in the configuration
107
- #
108
- # @raise [ConfigurationError] if not defined
109
106
  def logger
110
- config.logger || fail(ConfigurationError, 'No logger configured, see README for more details')
107
+ config.logger
111
108
  end
112
109
 
113
110
  def config
@@ -1,3 +1,4 @@
1
+ require 'songkick_queue'
1
2
  require 'songkick_queue/producer'
2
3
 
3
4
  module SongkickQueue
@@ -23,6 +24,28 @@ module SongkickQueue
23
24
  producer.publish(:queue_name, { example: 'message', value: true },
24
25
  message_id: '92c583bdc248', produced_at: '2015-03-30T15:41:55Z')
25
26
  end
27
+
28
+ it "should retry when publishing fails" do
29
+ producer = Producer.new
30
+
31
+ exchange = double(:exchange, publish: :published)
32
+ allow(exchange).to receive(:publish) { raise Bunny::ConnectionClosedError, 'test' }
33
+ client = instance_double(Client, default_exchange: exchange)
34
+ allow(producer).to receive(:client) { client }
35
+
36
+ logger = double(:logger, info: true)
37
+ allow(producer).to receive(:logger) { logger }
38
+
39
+ allow(producer).to receive(:wait_for_bunny_session_to_reconnect) { true }
40
+
41
+ config = double(:config, max_reconnect_attempts: 2)
42
+ allow(producer).to receive(:config) { config }
43
+
44
+ expect { producer.publish(:queue_name, { example: 'message', value: true }) }.to(
45
+ raise_error(TooManyReconnectAttemptsError, 'Attempted to reconnect more than 2 times'))
46
+
47
+ expect(producer.reconnect_attempts).to eq 3
48
+ end
26
49
  end
27
50
  end
28
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: songkick_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Lucraft
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-05-15 00:00:00.000000000 Z
12
+ date: 2015-05-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -100,7 +100,7 @@ files:
100
100
  - README.md
101
101
  - Rakefile
102
102
  - bin/songkick_queue
103
- - examples/environment.rb
103
+ - examples/consumer.rb
104
104
  - examples/producer.rb
105
105
  - lib/songkick_queue.rb
106
106
  - lib/songkick_queue/cli.rb