bunny 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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