bunny 1.1.5 → 1.1.6

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: 9d6d07747ed59a23a5fbef7a6146cb8f0ff7438a
4
- data.tar.gz: d6734646550b66ac0fa71e5c83f2e8575eb64c5a
3
+ metadata.gz: 36f1b18ef28b57485f1b419c8d5e325059b23769
4
+ data.tar.gz: f5ef937248905faf2040ff20b17fb9d72b492baa
5
5
  SHA512:
6
- metadata.gz: fc90357a545277a52e7d2a889d434def74fadef182547b6572f4aca8639312be02b84fd7a2eb8691be86a144c74c5bcb592adc951bc52a43604041a0135128e1
7
- data.tar.gz: 315c643ed31841055b93e1a8db3feb34ca7fe0023a2237dd6b7861e6b9236933d749bb90aaa96a0371a40248f02bc237963a7983a86057efc64d9ad0e22b492a
6
+ metadata.gz: 7a7f373fdb0ce3a05f3be3f39812cb20e69bf7a63011f4a66858650ff6783236600cde5fc8bad732f0b25df1e3810c99f12da31bbbec32b2dbf046907bec342e
7
+ data.tar.gz: 0347e24dfc932ae755d76672cb6b84828b2286b0790db0fc5c2eeda45f64dc70086776a9e397dc3581c9aeb4a8725cf924fa5249d4da1c89447d83141b056f2a
data/.travis.yml CHANGED
@@ -3,7 +3,7 @@ bundler_args: --without development
3
3
  before_script: "./bin/ci/before_build.sh"
4
4
  script: "bundle exec rspec -cfs spec"
5
5
  rvm:
6
- - "2.1.0-preview2"
6
+ - "2.1.0"
7
7
  - "2.0"
8
8
  - "1.9.3"
9
9
  - "jruby"
@@ -17,9 +17,10 @@ services:
17
17
  branches:
18
18
  only:
19
19
  - master
20
- - 0.9.x-stable
20
+ - 1.1.x-stable
21
21
  matrix:
22
22
  allow_failures:
23
23
  - rvm: rbx
24
+ - rvm: "1.9.2"
24
25
  - rvm: "1.8.7"
25
26
  - rvm: jruby
data/ChangeLog.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## Changes between Bunny 1.1.5 and 1.1.6
2
+
3
+ ### Cherry-picked Missing Commit from Master
4
+
5
+ `Bunny::Session#clean_up_on_shutdown` was cherry-picked from master.
6
+
7
+ ### Routing Key Limit
8
+
9
+ Per AMQP 0-9-1 spec, routing keys cannot be longer than 255 characters.
10
+ `Bunny::Channel#basic_publish` and `Bunny::Exchange#publish` now enforces
11
+ this limit.
12
+
13
+
1
14
  ## Changes between Bunny 1.1.4 and 1.1.5
2
15
 
3
16
  ### Thread Leak Plugged
@@ -18,6 +31,7 @@ Contributed by Brian Abreu.
18
31
 
19
32
  ## Changes between Bunny 1.1.2 and 1.1.3
20
33
 
34
+
21
35
  ### Nagle's Algorithm Disabled Correctly
22
36
 
23
37
  Bunny now properly disables [Nagle's algorithm](http://boundary.com/blog/2012/05/02/know-a-delay-nagles-algorithm-and-you/)
data/Gemfile CHANGED
@@ -35,6 +35,7 @@ end
35
35
 
36
36
  group :test do
37
37
  gem "rspec", ">= 2.13.0"
38
+ gem "rabbitmq_http_api_client", "~> 1.0.0"
38
39
  end
39
40
 
40
41
  gemspec
data/README.md CHANGED
@@ -92,7 +92,7 @@ gem install bunny
92
92
  To use Bunny in a project managed with Bundler:
93
93
 
94
94
  ``` ruby
95
- gem "bunny", "~> 1.0.4"
95
+ gem "bunny", "~> 1.1.0"
96
96
  ```
97
97
 
98
98
 
@@ -27,7 +27,7 @@ loop do
27
27
  rk = ["abc", "def"].sample
28
28
 
29
29
  begin
30
- x.publish(rand.to_s, :routing_key => rk)
30
+ x.publish(data, :routing_key => rk)
31
31
  puts "Published #{data}, routing key: #{rk}"
32
32
  # happens when a message is published before the connection
33
33
  # is recovered
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require 'bunny'
10
+
11
+ HIGH_PRIORITY_Q = "bunny.examples.priority.hilo.high"
12
+ LOW_PRIORITY_Q = "bunny.examples.priority.hilo.low"
13
+
14
+ conn = Bunny.new(:heartbeat_interval => 8)
15
+ conn.start
16
+
17
+ ch1 = conn.create_channel
18
+ ch2 = conn.create_channel
19
+ hi_q = ch1.queue(HIGH_PRIORITY_Q, :durable => false)
20
+ lo_q = ch2.queue(LOW_PRIORITY_Q, :durable => false)
21
+
22
+ ch3 = conn.create_channel
23
+ x = ch3.default_exchange
24
+
25
+ # create a backlog of low priority messages
26
+ 30.times do
27
+ x.publish(rand.to_s, :routing_key => LOW_PRIORITY_Q)
28
+ end
29
+
30
+ # and a much smaller one of high priority messages
31
+ 3.times do
32
+ x.publish(rand.to_s, :routing_key => HIGH_PRIORITY_Q)
33
+ end
34
+
35
+ hi_q.subscribe do |delivery_info, metadata, payload|
36
+ puts "[high] Consumed #{payload}"
37
+ end
38
+
39
+ lo_q.subscribe do |delivery_info, metadata, payload|
40
+ puts "[low] Consumed #{payload}"
41
+ end
42
+
43
+ loop do
44
+ sleep 0.5
45
+ data = rand.to_s
46
+ rk = [HIGH_PRIORITY_Q, LOW_PRIORITY_Q].sample
47
+
48
+ x.publish(data, :routing_key => rk)
49
+ puts "Published #{data}, routing key: #{rk}"
50
+ end
data/lib/bunny/channel.rb CHANGED
@@ -158,6 +158,7 @@ module Bunny
158
158
  attr_reader :consumers
159
159
 
160
160
  DEFAULT_CONTENT_TYPE = "application/octet-stream".freeze
161
+ SHORTSTR_LIMIT = 255
161
162
 
162
163
  # @param [Bunny::Session] connection AMQP 0.9.1 connection
163
164
  # @param [Integer] id Channel id, pass nil to make Bunny automatically allocate it
@@ -516,6 +517,7 @@ module Bunny
516
517
  # @api public
517
518
  def basic_publish(payload, exchange, routing_key, opts = {})
518
519
  raise_if_no_longer_open!
520
+ raise ArgumentError, "routing key cannot be longer than #{SHORTSTR_LIMIT} characters" if routing_key && routing_key.size > SHORTSTR_LIMIT
519
521
 
520
522
  exchange_name = if exchange.respond_to?(:name)
521
523
  exchange.name
@@ -1505,6 +1507,16 @@ module Bunny
1505
1507
  @recoveries_counter.increment
1506
1508
  end
1507
1509
 
1510
+ # @api public
1511
+ def recover_cancelled_consumers!
1512
+ @recover_cancelled_consumers = true
1513
+ end
1514
+
1515
+ # @api public
1516
+ def recovers_cancelled_consumers?
1517
+ !!@recover_cancelled_consumers
1518
+ end
1519
+
1508
1520
  # @endgroup
1509
1521
 
1510
1522
 
@@ -1575,8 +1587,15 @@ module Bunny
1575
1587
  if consumer = @consumers[method.consumer_tag]
1576
1588
  @work_pool.submit do
1577
1589
  begin
1578
- @consumers.delete(method.consumer_tag)
1579
- consumer.handle_cancellation(method)
1590
+ if recovers_cancelled_consumers?
1591
+ consumer.handle_cancellation(method)
1592
+ @logger.info "Automatically recovering cancelled consumer #{consumer.consumer_tag} on queue #{consumer.queue_name}"
1593
+
1594
+ consume_with(consumer)
1595
+ else
1596
+ @consumers.delete(method.consumer_tag)
1597
+ consumer.handle_cancellation(method)
1598
+ end
1580
1599
  rescue Exception => e
1581
1600
  @logger.error "Got exception when notifying consumer #{method.consumer_tag} about cancellation!"
1582
1601
  @uncaught_exception_handler.call(e, consumer) if @uncaught_exception_handler
@@ -39,6 +39,8 @@ module Bunny
39
39
  @arguments = arguments
40
40
  # no_ack set to true = no manual ack = automatic ack. MK.
41
41
  @no_ack = no_ack
42
+
43
+ @on_cancellation = []
42
44
  end
43
45
 
44
46
  # Defines message delivery handler
@@ -61,14 +63,16 @@ module Bunny
61
63
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
62
64
  # @api public
63
65
  def on_cancellation(&block)
64
- @on_cancellation = block
66
+ @on_cancellation << block
65
67
  self
66
68
  end
67
69
 
68
70
  # Invokes consumer cancellation notification handler
69
71
  # @private
70
72
  def handle_cancellation(basic_cancel)
71
- @on_cancellation.call(basic_cancel) if @on_cancellation
73
+ @on_cancellation.each do |fn|
74
+ fn.call(basic_cancel)
75
+ end
72
76
  end
73
77
 
74
78
  # Cancels this consumer. Messages for this consumer will no longer be delivered. If the queue
@@ -102,6 +106,16 @@ module Bunny
102
106
  @no_ack == false
103
107
  end
104
108
 
109
+ # @return [String] Name of the queue this consumer is on
110
+ # @api public
111
+ def queue_name
112
+ if @queue.respond_to?(:name)
113
+ @queue.name
114
+ else
115
+ @queue
116
+ end
117
+ end
118
+
105
119
  #
106
120
  # Recovery
107
121
  #
@@ -110,14 +124,5 @@ module Bunny
110
124
  def recover_from_network_failure
111
125
  @channel.basic_consume_with(self)
112
126
  end
113
-
114
- # @private
115
- def queue_name
116
- if @queue.respond_to?(:name)
117
- @queue.name
118
- else
119
- @queue
120
- end
121
- end
122
127
  end
123
128
  end
data/lib/bunny/session.rb CHANGED
@@ -146,6 +146,7 @@ module Bunny
146
146
  opts[:automatically_recover] || opts[:automatic_recovery]
147
147
  end
148
148
  @network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
149
+ @recover_from_connection_close = opts.fetch(:recover_from_connection_close, false)
149
150
  # in ms
150
151
  @continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT)
151
152
 
@@ -219,6 +220,11 @@ module Bunny
219
220
  @transport.configure_socket(&block)
220
221
  end
221
222
 
223
+ # @return [Integer] Client socket port
224
+ def local_port
225
+ @transport.local_address.ip_port
226
+ end
227
+
222
228
  # Starts the connection process.
223
229
  #
224
230
  # @see http://rubybunny.info/articles/getting_started.html
@@ -519,21 +525,13 @@ module Bunny
519
525
  when AMQ::Protocol::Channel::CloseOk then
520
526
  @continuations.push(method)
521
527
  when AMQ::Protocol::Connection::Close then
522
- @last_connection_error = instantiate_connection_level_exception(method)
523
- @continuations.push(method)
524
-
525
- begin
526
- shut_down_all_consumer_work_pools!
527
- maybe_shutdown_reader_loop
528
- rescue ShutdownSignal => sse
529
- # no-op
530
- rescue Exception => e
531
- @logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}"
532
- ensure
533
- close_transport
528
+ if recover_from_connection_close?
529
+ @logger.warn "Recovering from connection.close (#{method.reply_text})"
530
+ clean_up_on_shutdown
531
+ handle_network_failure(instantiate_connection_level_exception(method))
532
+ else
533
+ clean_up_and_fail_on_connection_close!(method)
534
534
  end
535
-
536
- @origin_thread.raise(@last_connection_error)
537
535
  when AMQ::Protocol::Connection::CloseOk then
538
536
  @last_connection_close_ok = method
539
537
  begin
@@ -590,6 +588,11 @@ module Bunny
590
588
  end
591
589
  end
592
590
 
591
+ # @private
592
+ def recover_from_connection_close?
593
+ @recover_from_connection_close
594
+ end
595
+
593
596
  # @private
594
597
  def handle_network_failure(exception)
595
598
  raise NetworkErrorWrapper.new(exception) unless @threaded
@@ -597,17 +600,21 @@ module Bunny
597
600
  @status = :disconnected
598
601
 
599
602
  if !recovering_from_network_failure?
600
- @recovering_from_network_failure = true
601
- if recoverable_network_failure?(exception)
602
- @logger.warn "Recovering from a network failure..."
603
- @channels.each do |n, ch|
604
- ch.maybe_kill_consumer_work_pool!
603
+ begin
604
+ @recovering_from_network_failure = true
605
+ if recoverable_network_failure?(exception)
606
+ @logger.warn "Recovering from a network failure..."
607
+ @channels.each do |n, ch|
608
+ ch.maybe_kill_consumer_work_pool!
609
+ end
610
+ maybe_shutdown_heartbeat_sender
611
+
612
+ recover_from_network_failure
613
+ else
614
+ # TODO: investigate if we can be a bit smarter here. MK.
605
615
  end
606
- maybe_shutdown_heartbeat_sender
607
-
608
- recover_from_network_failure
609
- else
610
- # TODO: investigate if we can be a bit smarter here. MK.
616
+ ensure
617
+ @recovering_from_network_failure = false
611
618
  end
612
619
  end
613
620
  end
@@ -684,6 +691,27 @@ module Bunny
684
691
  end
685
692
  end
686
693
 
694
+ def clean_up_and_fail_on_connection_close!(method)
695
+ @last_connection_error = instantiate_connection_level_exception(method)
696
+ @continuations.push(method)
697
+
698
+ clean_up_on_shutdown
699
+ @origin_thread.raise(@last_connection_error)
700
+ end
701
+
702
+ def clean_up_on_shutdown
703
+ begin
704
+ shut_down_all_consumer_work_pools!
705
+ maybe_shutdown_reader_loop
706
+ rescue ShutdownSignal => sse
707
+ # no-op
708
+ rescue Exception => e
709
+ @logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}"
710
+ ensure
711
+ close_transport
712
+ end
713
+ end
714
+
687
715
  # @private
688
716
  def hostname_from(options)
689
717
  options[:host] || options[:hostname] || DEFAULT_HOST
@@ -55,6 +55,10 @@ module Bunny
55
55
  @host
56
56
  end
57
57
 
58
+ def local_address
59
+ @socket.local_address
60
+ end
61
+
58
62
  def uses_tls?
59
63
  @tls_enabled
60
64
  end
data/lib/bunny/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "1.1.5"
5
+ VERSION = "1.1.6"
6
6
  end
@@ -87,3 +87,77 @@ if RUBY_VERSION <= "1.9"
87
87
  end
88
88
  end
89
89
  end
90
+
91
+
92
+ describe "Published message" do
93
+ let(:connection) do
94
+ c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
95
+ c.start
96
+ c
97
+ end
98
+
99
+ after :each do
100
+ connection.close if connection.open?
101
+ end
102
+
103
+ context "with all default delivery and a 254 character long routing key" do
104
+ it "routes the messages" do
105
+ ch = connection.create_channel
106
+
107
+ q = ch.queue("", :exclusive => true)
108
+ x = ch.fanout("amq.fanout")
109
+ q.bind(x)
110
+
111
+ rk = "a" * 254
112
+ x.publish("xyzzy", :routing_key => rk, :persistent => true)
113
+
114
+ sleep(1)
115
+ q.message_count.should == 1
116
+
117
+ _, _, payload = q.pop
118
+
119
+ payload.should == "xyzzy"
120
+
121
+ ch.close
122
+ end
123
+ end
124
+
125
+ context "with all default delivery and a 255 character long routing key" do
126
+ it "routes the messages" do
127
+ ch = connection.create_channel
128
+
129
+ q = ch.queue("", :exclusive => true)
130
+ x = ch.fanout("amq.fanout")
131
+ q.bind(x)
132
+
133
+ rk = "a" * 255
134
+ x.publish("xyzzy", :routing_key => rk, :persistent => true)
135
+
136
+ sleep(1)
137
+ q.message_count.should == 1
138
+
139
+ _, _, payload = q.pop
140
+
141
+ payload.should == "xyzzy"
142
+
143
+ ch.close
144
+ end
145
+ end
146
+
147
+ context "with all default delivery and a 256 character long routing key" do
148
+ it "fails with a connection exception" do
149
+ ch = connection.create_channel
150
+
151
+ q = ch.queue("", :exclusive => true)
152
+ x = ch.fanout("amq.fanout")
153
+ q.bind(x)
154
+
155
+ rk = "a" * 256
156
+ expect do
157
+ x.publish("xyzzy", :routing_key => rk, :persistent => true)
158
+ end.to raise_error(ArgumentError)
159
+
160
+ ch.close
161
+ end
162
+ end
163
+ end
@@ -1,6 +1,20 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Bunny::Session do
4
+ let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") }
5
+
6
+ def close_connection(client_port)
7
+ c = http_client.
8
+ list_connections.
9
+ find { |conn_info| conn_info.peer_port.to_i == client_port }
10
+
11
+ http_client.close_connection(c.name)
12
+ end
13
+
14
+ def wait_for_recovery
15
+ sleep 0.5
16
+ end
17
+
4
18
  it "can be closed" do
5
19
  c = Bunny.new(:automatically_recover => false)
6
20
  c.start
@@ -10,17 +24,48 @@ describe Bunny::Session do
10
24
  c.stop
11
25
  c.should be_closed
12
26
  end
13
- end
14
-
15
27
 
16
- describe Bunny::Session, "in a single threaded mode" do
17
- it "can be closed" do
18
- c = Bunny.new(:automatically_recover => false, :threaded => false)
28
+ it "can be closed twice (Session#close is idempotent)" do
29
+ c = Bunny.new(:automatically_recover => false)
19
30
  c.start
20
31
  ch = c.create_channel
21
32
 
22
33
  c.should be_connected
23
34
  c.stop
24
35
  c.should be_closed
36
+ c.stop
37
+ c.should be_closed
38
+ end
39
+
40
+ describe "in a single threaded mode" do
41
+ it "can be closed" do
42
+ c = Bunny.new(:automatically_recover => false, :threaded => false)
43
+ c.start
44
+ ch = c.create_channel
45
+
46
+ c.should be_connected
47
+ c.stop
48
+ c.should be_closed
49
+ end
50
+ end
51
+
52
+
53
+ describe "that recovers from connection.close" do
54
+ it "can be closed" do
55
+ c = Bunny.new(:automatically_recover => false, :recover_from_connection_close => true, :network_recovery_interval => 0.2)
56
+ c.start
57
+ ch = c.create_channel
58
+
59
+ c.should be_open
60
+ close_connection(c.local_port)
61
+ sleep 0.2
62
+ c.should_not be_open
63
+
64
+ wait_for_recovery
65
+ c.should be_open
66
+ ch.should be_open
67
+
68
+ c.close
69
+ end
25
70
  end
26
71
  end
@@ -66,4 +66,19 @@ describe Bunny::Channel, "#basic_cancel" do
66
66
  ch.close
67
67
  end
68
68
  end
69
+
70
+ context "when the given consumer tag belongs to a different channel" do
71
+ it "DOES NOT cause a channel error" do
72
+ ch1 = connection.create_channel
73
+ ch2 = connection.create_channel
74
+
75
+ q = ch1.queue("", :exclusive => true)
76
+ cons = q.subscribe do |_, _, _|
77
+ end
78
+ ch2.basic_cancel(cons.consumer_tag)
79
+
80
+ ch1.close
81
+ ch2.close
82
+ end
83
+ end
69
84
  end
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,7 @@ Bundler.setup(:default, :test)
8
8
 
9
9
  require "effin_utf8"
10
10
  require "bunny"
11
+ require "rabbitmq/http/client"
11
12
 
12
13
 
13
14
  require "amq/protocol/version"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -75,6 +75,7 @@ files:
75
75
  - examples/connection/manually_reconnecting_consumer.rb
76
76
  - examples/connection/manually_reconnecting_publisher.rb
77
77
  - examples/connection/unknown_host.rb
78
+ - examples/consumers/high_and_low_priority.rb
78
79
  - examples/guides/exchanges/direct_exchange_routing.rb
79
80
  - examples/guides/exchanges/fanout_exchange_routing.rb
80
81
  - examples/guides/exchanges/headers_exchange_routing.rb