bunny 1.1.0.pre1 → 1.1.0.pre2

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: 840a6fefa94a5fef22bdc0a24178ba01498160bb
4
- data.tar.gz: 98b792aa4957d2109ce984742f7cb50551b6503d
3
+ metadata.gz: 843f81c989d0b413c14b8874bb6f2e4365da3921
4
+ data.tar.gz: b4fcd0d7a8e3effe884f2b3ecefc2db524aaf545
5
5
  SHA512:
6
- metadata.gz: 2f50640dcc07103b1f4d6c54230d27ad658bc8b6870874d781718cfca9910abca7c489bbd44acfb8f6f42a237ddf24afe7c484d6a36ab820d18ad846d1ae5585
7
- data.tar.gz: 40d9d577e9b65e43882011714cf5f956804818fc692b7d30adc5e131e97ee51ab5b1bcee07434dceb097a571436917bc602078aed331594b32231ef54a8bb2cc
6
+ metadata.gz: dee6d9086ff1654324c354037d1a35875b2ded96a1b0701c9836d5d2e4d41ff83df212ac5efca7643e50c30304f0be360ddbd4f6d64470634348b625a71f0016
7
+ data.tar.gz: 3fd0c4d92be3121789bf4dc7349583c24de9534cafb3a686fef5e080211c708a02ba15aef7e555c29c2dec2567439f8dcc11c6889da0788e272703b355b3ac28
@@ -1,5 +1,38 @@
1
+ ## Changes between Bunny 1.1.0.pre1 and 1.1.0.pre2
2
+
3
+ ### connection.tune.channel_max No Longer Overflows
4
+
5
+ `connection.tune.channel_max` could previously be configured to values
6
+ greater than 2^16 - 1 (65535). This would result in a silent overflow
7
+ during serialization. The issue was harmless in practice but is still
8
+ a bug that can be quite confusing.
9
+
10
+ Bunny now caps max number of channels to 65535. This allows it to be
11
+ forward compatible with future RabbitMQ versions that may allow limiting
12
+ total # of open channels via server configuration.
13
+
14
+ ### amq-protocol Update
15
+
16
+ Minimum `amq-protocol` version is now `1.9.0` which includes
17
+ bug fixes and performance improvements for channel ID allocator.
18
+
19
+ ### Thread Leaks Fixes
20
+
21
+ Bunny will now correctly release heartbeat sender when allocating
22
+ a new one (usually happens only when connection recovers from a network
23
+ failure).
24
+
25
+
1
26
  ## Changes between Bunny 1.0.0 and 1.1.0.pre1
2
27
 
28
+ ### Versioned Delivery Tag Fix
29
+
30
+ Versioned delivery tag now ensures all the arguments it operates
31
+ (original delivery tag, atomic fixnum instances, etc) are coerced to `Integer`
32
+ before comparison.
33
+
34
+ GitHub issues: #171.
35
+
3
36
  ### User-Provided Loggers
4
37
 
5
38
  Bunny now can use any logger that provides the same API as Ruby standard library's `Logger`:
data/README.md CHANGED
@@ -48,7 +48,7 @@ Specific examples:
48
48
 
49
49
  Bunny 0.9 and more recent versions support
50
50
 
51
- * CRuby 1.9.3, 1.9.2, 2.0.0, and 1.8.7
51
+ * CRuby 2.0.0, 1.9.3, 1.9.2, and 1.8.7
52
52
  * JRuby 1.7+
53
53
  * Rubinius 2.0+
54
54
 
@@ -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.0.rc2"
95
+ gem "bunny", "~> 1.0.4"
96
96
  ```
97
97
 
98
98
 
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
29
29
  map { |mail| Base64.decode64(mail) }
30
30
 
31
31
  # Dependencies
32
- s.add_dependency "amq-protocol", ">= 1.8.0"
32
+ s.add_dependency "amq-protocol", ">= 1.9.0"
33
33
 
34
34
  # Files.
35
35
  s.has_rdoc = true
@@ -0,0 +1,109 @@
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
+ conn = Bunny.new(:heartbeat_interval => 8)
12
+ conn.start
13
+
14
+ ch0 = conn.create_channel
15
+ ch1 = conn.create_channel
16
+ ch2 = conn.create_channel
17
+ ch3 = conn.create_channel
18
+
19
+ x = ch1.topic("hb.examples.recovery.topic", :durable => false)
20
+ q1 = ch1.queue("hb.examples.recovery.client_named_queue1", :durable => false)
21
+ q2 = ch2.queue("hb.examples.recovery.client_named_queue2", :durable => false)
22
+ q3 = ch3.queue("hb.examples.recovery.client_named_queue3", :durable => false)
23
+
24
+ q1.bind(x, :routing_key => "abc")
25
+ q2.bind(x, :routing_key => "def")
26
+ q3.bind(x, :routing_key => "xyz")
27
+
28
+ x0 = ch0.fanout("hb.examples.recovery.fanout0")
29
+ x1 = ch1.fanout("hb.examples.recovery.fanout1")
30
+ x2 = ch2.fanout("hb.examples.recovery.fanout2")
31
+ x3 = ch3.fanout("hb.examples.recovery.fanout3")
32
+
33
+ q4 = ch1.queue("", :exclusive => true)
34
+ q4.bind(x0)
35
+
36
+ q5 = ch2.queue("", :exclusive => true)
37
+ q5.bind(x1)
38
+
39
+ q6 = ch3.queue("", :exclusive => true)
40
+ q6.bind(x2)
41
+ q6.bind(x3)
42
+
43
+
44
+ q1.subscribe do |delivery_info, metadata, payload|
45
+ puts "[Q1] Consumed #{payload} on channel #{q1.channel.id}"
46
+ if ch0.open?
47
+ puts "Publishing a reply on channel #{ch0.id} which is open"
48
+ x0.publish(Time.now.to_i.to_s)
49
+ end
50
+ end
51
+
52
+ q2.subscribe do |delivery_info, metadata, payload|
53
+ puts "[Q2] Consumed #{payload} on channel #{q2.channel.id}"
54
+
55
+ if ch1.open?
56
+ puts "Publishing a reply on channel #{ch1.id} which is open"
57
+ x1.publish(Time.now.to_i.to_s)
58
+ end
59
+ end
60
+
61
+ q3.subscribe do |delivery_info, metadata, payload|
62
+ puts "[Q3] Consumed #{payload} (consumer 1, channel #{q3.channel.id})"
63
+
64
+ if ch2.open?
65
+ puts "Publishing a reply on channel #{ch1.id} which is open"
66
+ x2.publish(Time.now.to_i.to_s)
67
+ end
68
+ end
69
+
70
+ q3.subscribe do |delivery_info, metadata, payload|
71
+ puts "[Q3] Consumed #{payload} (consumer 2, channel #{q3.channel.id})"
72
+
73
+ if ch3.open?
74
+ puts "Publishing a reply on channel #{ch3.id} which is open"
75
+ x3.publish(Time.now.to_i.to_s)
76
+ end
77
+ end
78
+
79
+ q4.subscribe do |delivery_info, metadata, payload|
80
+ puts "[Q4] Consumed #{payload} on channel #{q4.channel.id}"
81
+ end
82
+
83
+ q5.subscribe do |delivery_info, metadata, payload|
84
+ puts "[Q5] Consumed #{payload} on channel #{q5.channel.id}"
85
+ end
86
+
87
+ q6.subscribe do |delivery_info, metadata, payload|
88
+ puts "[Q6] Consumed #{payload} on channel #{q6.channel.id}"
89
+ end
90
+
91
+ loop do
92
+ sleep 1
93
+ data = rand.to_s
94
+ rk = ["abc", "def", "xyz", Time.now.to_i.to_s].sample
95
+
96
+ begin
97
+ 3.times do
98
+ x.publish(rand.to_s, :routing_key => rk)
99
+ puts "Published #{data}, routing key: #{rk} on channel #{x.channel.id}"
100
+ end
101
+ # happens when a message is published before the connection
102
+ # is recovered
103
+ rescue Exception => e
104
+ puts "Exception: #{e.message}"
105
+ # e.backtrace.each do |line|
106
+ # puts "\t#{line}"
107
+ # end
108
+ end
109
+ end
@@ -42,6 +42,18 @@ module Bunny
42
42
  end
43
43
  end
44
44
 
45
+ # Can indicate either a channel or connection-level issue
46
+ class NotAllowedError < Exception
47
+ attr_reader :connection, :connection_close
48
+
49
+ def initialize(message, connection, connection_close = nil)
50
+ super(message)
51
+
52
+ @connection = connection
53
+ @connection_close = connection_close
54
+ end
55
+ end
56
+
45
57
 
46
58
  # Raised when TCP connection to RabbitMQ fails because of an unresolved
47
59
  # hostname, connectivity problem, etc
@@ -36,6 +36,10 @@ module Bunny
36
36
  DEFAULT_HEARTBEAT = :server
37
37
  # @private
38
38
  DEFAULT_FRAME_MAX = 131072
39
+ # 2^16 - 1, maximum representable signed 16 bit integer.
40
+ # @private
41
+ CHANNEL_MAX_LIMIT = 65535
42
+ DEFAULT_CHANNEL_MAX = CHANNEL_MAX_LIMIT
39
43
 
40
44
  # backwards compatibility
41
45
  # @private
@@ -78,7 +82,7 @@ module Bunny
78
82
 
79
83
  # @return [Bunny::Transport]
80
84
  attr_reader :transport
81
- attr_reader :status, :host, :port, :heartbeat, :user, :pass, :vhost, :frame_max, :threaded
85
+ attr_reader :status, :host, :port, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded
82
86
  attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
83
87
  attr_reader :default_channel
84
88
  attr_reader :channel_id_allocator
@@ -150,7 +154,9 @@ module Bunny
150
154
 
151
155
  # these are negotiated with the broker during the connection tuning phase
152
156
  @client_frame_max = opts.fetch(:frame_max, DEFAULT_FRAME_MAX)
153
- @client_channel_max = opts.fetch(:channel_max, 65536)
157
+ @client_channel_max = normalize_client_channel_max(opts.fetch(:channel_max, DEFAULT_CHANNEL_MAX))
158
+ # will be-renegotiated during connection tuning steps. MK.
159
+ @channel_max = @client_channel_max
154
160
  @client_heartbeat = self.heartbeat_from(opts)
155
161
 
156
162
  @client_properties = opts[:properties] || DEFAULT_CLIENT_PROPERTIES
@@ -288,9 +294,10 @@ module Bunny
288
294
 
289
295
  maybe_shutdown_reader_loop
290
296
  close_transport
291
-
292
- @status = :closed
293
297
  end
298
+
299
+ shut_down_all_consumer_work_pools!
300
+ @status = :closed
294
301
  end
295
302
  alias stop close
296
303
 
@@ -480,13 +487,14 @@ module Bunny
480
487
  if @transport.open?
481
488
  @transport.send_frame(AMQ::Protocol::Connection::Close.encode(200, "Goodbye", 0, 0))
482
489
 
483
- maybe_shutdown_heartbeat_sender
484
- @status = :not_connected
485
-
486
490
  if sync
487
491
  @last_connection_close_ok = wait_on_continuations
488
492
  end
489
493
  end
494
+
495
+ shut_down_all_consumer_work_pools!
496
+ maybe_shutdown_heartbeat_sender
497
+ @status = :not_connected
490
498
  end
491
499
 
492
500
  # Handles incoming frames and dispatches them.
@@ -508,6 +516,17 @@ module Bunny
508
516
  @last_connection_error = instantiate_connection_level_exception(method)
509
517
  @continuations.push(method)
510
518
 
519
+ begin
520
+ shut_down_all_consumer_work_pools!
521
+ maybe_shutdown_reader_loop
522
+ rescue ShutdownSignal => sse
523
+ # no-op
524
+ rescue Exception => e
525
+ @logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}"
526
+ ensure
527
+ close_transport
528
+ end
529
+
511
530
  @origin_thread.raise(@last_connection_error)
512
531
  when AMQ::Protocol::Connection::CloseOk then
513
532
  @last_connection_close_ok = method
@@ -647,6 +666,8 @@ module Bunny
647
666
  UnexpectedFrame
648
667
  when 506 then
649
668
  ResourceError
669
+ when 530 then
670
+ NotAllowedError
650
671
  when 541 then
651
672
  InternalError
652
673
  else
@@ -938,7 +959,25 @@ module Bunny
938
959
  initialize_heartbeat_sender
939
960
  end
940
961
 
941
- raise "could not open connection: server did not respond with connection.open-ok" unless connection_open_ok.is_a?(AMQ::Protocol::Connection::OpenOk)
962
+ unless connection_open_ok.is_a?(AMQ::Protocol::Connection::OpenOk)
963
+ if connection_open_ok.is_a?(AMQ::Protocol::Connection::Close)
964
+ e = instantiate_connection_level_exception(connection_open_ok)
965
+ begin
966
+ shut_down_all_consumer_work_pools!
967
+ maybe_shutdown_reader_loop
968
+ rescue ShutdownSignal => sse
969
+ # no-op
970
+ rescue Exception => e
971
+ @logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}"
972
+ ensure
973
+ close_transport
974
+ end
975
+
976
+ @origin_thread.raise(e)
977
+ else
978
+ raise "could not open connection: server did not respond with connection.open-ok but #{connection_open_ok.inspect} instead"
979
+ end
980
+ end
942
981
  end
943
982
 
944
983
  def heartbeat_disabled?(val)
@@ -958,6 +997,7 @@ module Bunny
958
997
 
959
998
  # @private
960
999
  def initialize_heartbeat_sender
1000
+ maybe_shutdown_heartbeat_sender
961
1001
  @logger.debug "Initializing heartbeat sender..."
962
1002
  @heartbeat_sender = HeartbeatSender.new(@transport, @logger)
963
1003
  @heartbeat_sender.start(@heartbeat)
@@ -1038,6 +1078,24 @@ module Bunny
1038
1078
  Logger::WARN
1039
1079
  end
1040
1080
  end
1081
+
1082
+ # @private
1083
+ def shut_down_all_consumer_work_pools!
1084
+ @channels.each do |_, ch|
1085
+ ch.maybe_kill_consumer_work_pool!
1086
+ end
1087
+ end
1088
+
1089
+ def normalize_client_channel_max(n)
1090
+ return CHANNEL_MAX_LIMIT if n > CHANNEL_MAX_LIMIT
1091
+
1092
+ case n
1093
+ when 0 then
1094
+ CHANNEL_MAX_LIMIT
1095
+ else
1096
+ n
1097
+ end
1098
+ end
1041
1099
  end # Session
1042
1100
 
1043
1101
  # backwards compatibility
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "1.1.0.pre1"
5
+ VERSION = "1.1.0.pre2"
6
6
  end
@@ -147,6 +147,25 @@ describe Bunny::Session do
147
147
  end
148
148
  end
149
149
 
150
+ context "initialized with :channel_max => 4096" do
151
+ after :each do
152
+ subject.close if subject.open?
153
+ end
154
+
155
+ let(:channel_max) { 1024 }
156
+ let(:subject) { described_class.new(:channel_max => channel_max) }
157
+
158
+ # this assumes RabbitMQ has no lower value configured. In 3.2
159
+ # it is 0 (no limit) by default and 1024 is still a fairly low value
160
+ # for future releases. MK.
161
+ it "negotiates channel max to be 1024" do
162
+ subject.start
163
+ subject.channel_max.should == channel_max
164
+
165
+ subject.close
166
+ end
167
+ end
168
+
150
169
  context "initialized with :ssl => true" do
151
170
  let(:subject) do
152
171
  described_class.new(:user => "bunny_gem",
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.0.pre1
4
+ version: 1.1.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2013-11-18 00:00:00.000000000 Z
15
+ date: 2013-11-25 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol
@@ -20,14 +20,14 @@ dependencies:
20
20
  requirements:
21
21
  - - '>='
22
22
  - !ruby/object:Gem::Version
23
- version: 1.8.0
23
+ version: 1.9.0
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
28
  - - '>='
29
29
  - !ruby/object:Gem::Version
30
- version: 1.8.0
30
+ version: 1.9.0
31
31
  description: Easy to use, feature complete Ruby client for RabbitMQ 2.0 and later
32
32
  versions.
33
33
  email:
@@ -67,6 +67,7 @@ files:
67
67
  - examples/connection/automatic_recovery_with_basic_get.rb
68
68
  - examples/connection/automatic_recovery_with_client_named_queues.rb
69
69
  - examples/connection/automatic_recovery_with_multiple_consumers.rb
70
+ - examples/connection/automatic_recovery_with_republishing.rb
70
71
  - examples/connection/automatic_recovery_with_server_named_queues.rb
71
72
  - examples/connection/channel_level_exception.rb
72
73
  - examples/connection/disabled_automatic_recovery.rb
@@ -224,7 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
225
  version: 1.3.1
225
226
  requirements: []
226
227
  rubyforge_project:
227
- rubygems_version: 2.1.6
228
+ rubygems_version: 2.1.11
228
229
  signing_key:
229
230
  specification_version: 4
230
231
  summary: Popular easy to use Ruby client for RabbitMQ