Hello community, here is the log from the commit of package rubygem-bunny for openSUSE:Factory checked in at 2016-08-26 23:16:42 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-bunny (Old) and /work/SRC/openSUSE:Factory/.rubygem-bunny.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "rubygem-bunny" Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-bunny/rubygem-bunny.changes 2016-06-14 23:08:07.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-bunny.new/rubygem-bunny.changes 2016-08-26 23:16:45.000000000 +0200 @@ -1,0 +2,11 @@ +Sat Jul 23 09:01:36 UTC 2016 - jengelh@inai.de + +- Run fdupes to elide duplicate documentation files + +------------------------------------------------------------------- +Thu Jul 21 04:29:18 UTC 2016 - coolo@suse.com + +- updated to version 2.5.0 + see installed ChangeLog.md + +------------------------------------------------------------------- Old: ---- bunny-2.4.0.gem New: ---- bunny-2.5.0.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-bunny.spec ++++++ --- /var/tmp/diff_new_pack.25Xpzs/_old 2016-08-26 23:16:46.000000000 +0200 +++ /var/tmp/diff_new_pack.25Xpzs/_new 2016-08-26 23:16:46.000000000 +0200 @@ -24,13 +24,14 @@ # Name: rubygem-bunny -Version: 2.4.0 +Version: 2.5.0 Release: 0 %define mod_name bunny %define mod_full_name %{mod_name}-%{version} BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: %{ruby >= 2.0} BuildRequires: %{rubygem gem2rpm} +BuildRequires: fdupes BuildRequires: ruby-macros >= 5 Url: http://rubybunny.info Source: http://rubygems.org/gems/%{mod_full_name}.gem @@ -50,6 +51,7 @@ %gem_install \ --doc-files="ChangeLog.md LICENSE README.md" \ -f +%fdupes %buildroot/%_prefix %gem_packages ++++++ bunny-2.4.0.gem -> bunny-2.5.0.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/CONTRIBUTING.md new/CONTRIBUTING.md --- old/CONTRIBUTING.md 2016-06-11 23:24:31.000000000 +0200 +++ new/CONTRIBUTING.md 2016-07-20 13:34:54.000000000 +0200 @@ -55,7 +55,7 @@ ### Running Test Suites Prior to running the tests, configure the RabbitMQ permissions -by running `./bin/ci/before_script`. The script uses `rabbitmqctl` and `rabbitmq-plugins` +by running `./bin/ci/before_build`. The script uses `rabbitmqctl` and `rabbitmq-plugins` to set up RabbitMQ in a way that Bunny test suites expect. Two environment variables, `RABBITMQCTL` and `RABBITMQ_PLUGINS`, are available to control what `rabbitmqctl` and `rabbitmq-plugins` commands will be used. By default they are taken from `PATH` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ChangeLog.md new/ChangeLog.md --- old/ChangeLog.md 2016-06-11 23:24:31.000000000 +0200 +++ new/ChangeLog.md 2016-07-20 13:34:54.000000000 +0200 @@ -1,4 +1,9 @@ -## Changes between Bunny 2.3.0 and 2.4.0 (unreleased) +## Changes between Bunny 2.4.0 and 2.5.0 (unreleased) + +No changes yet. + + +## Changes between Bunny 2.3.0 and 2.4.0 (June 11th, 2016) ### Unconfirmed Delivery Tag Set Reset on Network Recovery Files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bunny/channel.rb new/lib/bunny/channel.rb --- old/lib/bunny/channel.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/lib/bunny/channel.rb 2016-07-20 13:34:54.000000000 +0200 @@ -1783,6 +1783,8 @@ # @private def wait_on_confirms_continuations + raise_if_no_longer_open! + if @connection.threaded t = Thread.current @threads_waiting_on_confirms_continuations << t diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bunny/exchange.rb new/lib/bunny/exchange.rb --- old/lib/bunny/exchange.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/lib/bunny/exchange.rb 2016-07-20 13:34:54.000000000 +0200 @@ -1,3 +1,5 @@ +require 'amq/protocol' + module Bunny # Represents AMQP 0.9.1 exchanges. # @@ -80,6 +82,8 @@ @internal = @options[:internal] @arguments = @options[:arguments] + @bindings = Set.new + declare! unless opts[:no_declare] || predeclared? || (@name == AMQ::Protocol::EMPTY_STRING) @channel.register_exchange(self) @@ -169,6 +173,7 @@ # @api public def bind(source, opts = {}) @channel.exchange_bind(source, self, opts) + @bindings.add(source: source, opts: opts) self end @@ -189,6 +194,7 @@ # @api public def unbind(source, opts = {}) @channel.exchange_unbind(source, self, opts) + @bindings.delete(source: source, opts: opts) self end @@ -214,8 +220,11 @@ # @private def recover_from_network_failure - # puts "Recovering exchange #{@name} from network failure" - declare! unless predefined? + declare! unless @options[:no_declare] ||predefined? + + @bindings.each do |b| + bind(b[:source], b[:opts]) + end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bunny/queue.rb new/lib/bunny/queue.rb --- old/lib/bunny/queue.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/lib/bunny/queue.rb 2016-07-20 13:34:54.000000000 +0200 @@ -341,7 +341,7 @@ # TODO: inject and use logger # puts "Recovering queue #{@name}" begin - declare! + declare! unless @options[:no_declare] @channel.register_queue(self) rescue Exception => e diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bunny/session.rb new/lib/bunny/session.rb --- old/lib/bunny/session.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/lib/bunny/session.rb 2016-07-20 13:34:54.000000000 +0200 @@ -108,6 +108,7 @@ # @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem) # @option connection_string_or_opts [Array<String>] :tls_ca_certificates Array of paths to TLS/SSL CA files (.pem), by default detected from OpenSSL configuration # @option connection_string_or_opts [String] :verify_peer (true) Whether TLS peer verification should be performed + # @option connection_string_or_opts [Keyword] :tls_version (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2) # @option connection_string_or_opts [Integer] :continuation_timeout (15000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds. # @option connection_string_or_opts [Integer] :connection_timeout (5) Timeout in seconds for connecting to the server. # @option connection_string_or_opts [Proc] :hosts_shuffle_strategy A Proc that reorders a list of host strings, defaults to Array#shuffle @@ -668,30 +669,28 @@ # @private def recover_from_network_failure - begin - sleep @network_recovery_interval - @logger.debug "About to start connection recovery..." + sleep @network_recovery_interval + @logger.debug "About to start connection recovery..." - self.initialize_transport + self.initialize_transport - @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}" - self.start + @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}" + self.start - if open? - @recovering_from_network_failure = false + if open? + @recovering_from_network_failure = false - recover_channels - end - rescue HostListDepleted - reset_address_index - retry - rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e - @logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds" - sleep @network_recovery_interval - if should_retry_recovery? - @recovery_attempts -= 1 if @recovery_attempts - retry if recoverable_network_failure?(e) - end + recover_channels + end + rescue HostListDepleted + reset_address_index + retry + rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e + @logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds" + sleep @network_recovery_interval + if should_retry_recovery? + @recovery_attempts -= 1 if @recovery_attempts + retry if recoverable_network_failure?(e) end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bunny/version.rb new/lib/bunny/version.rb --- old/lib/bunny/version.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/lib/bunny/version.rb 2016-07-20 13:34:54.000000000 +0200 @@ -2,5 +2,5 @@ module Bunny # @return [String] Version of the library - VERSION = "2.4.0" + VERSION = "2.5.0" end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2016-06-11 23:24:31.000000000 +0200 +++ new/metadata 2016-07-20 13:34:54.000000000 +0200 @@ -1,7 +1,7 @@ --- !ruby/object:Gem::Specification name: bunny version: !ruby/object:Gem::Version - version: 2.4.0 + version: 2.5.0 platform: ruby authors: - Chris Duncan @@ -12,7 +12,7 @@ autorequire: bindir: bin cert_chain: [] -date: 2016-06-11 00:00:00.000000000 Z +date: 2016-07-20 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: amq-protocol @@ -150,7 +150,6 @@ - spec/higher_level_api/integration/basic_return_spec.rb - spec/higher_level_api/integration/channel_close_spec.rb - spec/higher_level_api/integration/channel_open_spec.rb -- spec/higher_level_api/integration/confirm_select_spec.rb - spec/higher_level_api/integration/connection_recovery_spec.rb - spec/higher_level_api/integration/connection_spec.rb - spec/higher_level_api/integration/connection_stop_spec.rb @@ -209,6 +208,7 @@ - spec/unit/concurrent/condition_spec.rb - spec/unit/concurrent/linked_continuation_queue_spec.rb - spec/unit/concurrent/synchronized_sorted_set_spec.rb +- spec/unit/exchange_recovery_spec.rb - spec/unit/version_delivery_tag_spec.rb homepage: http://rubybunny.info licenses: @@ -230,7 +230,7 @@ version: '0' requirements: [] rubyforge_project: -rubygems_version: 2.4.8 +rubygems_version: 2.5.1 signing_key: specification_version: 4 summary: Popular easy to use Ruby client for RabbitMQ @@ -250,7 +250,6 @@ - spec/higher_level_api/integration/basic_return_spec.rb - spec/higher_level_api/integration/channel_close_spec.rb - spec/higher_level_api/integration/channel_open_spec.rb -- spec/higher_level_api/integration/confirm_select_spec.rb - spec/higher_level_api/integration/connection_recovery_spec.rb - spec/higher_level_api/integration/connection_spec.rb - spec/higher_level_api/integration/connection_stop_spec.rb @@ -309,5 +308,6 @@ - spec/unit/concurrent/condition_spec.rb - spec/unit/concurrent/linked_continuation_queue_spec.rb - spec/unit/concurrent/synchronized_sorted_set_spec.rb +- spec/unit/exchange_recovery_spec.rb - spec/unit/version_delivery_tag_spec.rb has_rdoc: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/higher_level_api/integration/confirm_select_spec.rb new/spec/higher_level_api/integration/confirm_select_spec.rb --- old/spec/higher_level_api/integration/confirm_select_spec.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/spec/higher_level_api/integration/confirm_select_spec.rb 1970-01-01 01:00:00.000000000 +0100 @@ -1,19 +0,0 @@ -require "spec_helper" - -describe Bunny::Channel, "#confirm_select" do - let(:connection) do - c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed") - c.start - c - end - - after :each do - connection.close if connection.open? - end - - it "is supported" do - connection.with_channel do |ch| - ch.confirm_select - end - end -end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/higher_level_api/integration/connection_recovery_spec.rb new/spec/higher_level_api/integration/connection_recovery_spec.rb --- old/spec/higher_level_api/integration/connection_recovery_spec.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/spec/higher_level_api/integration/connection_recovery_spec.rb 2016-07-20 13:34:54.000000000 +0200 @@ -1,403 +1,421 @@ require "spec_helper" require "rabbitmq/http/client" -unless ENV["CI"] - describe "Connection recovery" do - let(:connection) { } - let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") } - - def close_all_connections! - http_client.list_connections.each do |conn_info| - begin - http_client.close_connection(conn_info.name) - rescue Bunny::ConnectionForced - # This is not a problem, but the specs intermittently believe it is. - end - end +describe "Connection recovery" do + let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") } + let(:logger) { Logger.new($stderr).tap {|logger| logger.level = Logger::FATAL} } + let(:recovery_interval) { 0.2 } + + it "reconnects after grace period" do + with_open do |c| + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } end + end - def wait_for_recovery - sleep 1.5 + it "reconnects after grace period (with multiple hosts)" do + with_open_multi_host do |c| + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } end + end - def with_open(c = Bunny.new(:network_recovery_interval => 0.2, :recover_from_connection_close => true), &block) - begin - c.start - block.call(c) - ensure - c.close - end + it "reconnects after grace period (with multiple hosts, including a broken one)" do + with_open_multi_broken_host do |c| + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } end + end - def with_open_multi_host( c = Bunny.new( :hosts => ["127.0.0.1", "localhost"], - :network_recovery_interval => 0.2, - :recover_from_connection_close => true), &block) - begin - c.start - block.call(c) - ensure - c.close - end + it "recovers channels" do + with_open do |c| + ch1 = c.create_channel + ch2 = c.create_channel + close_all_connections! + poll_until { channels.count.zero? } + poll_until { channels.count == 2 } + expect(ch1).to be_open + expect(ch2).to be_open end + end - def with_open_multi_broken_host( c = Bunny.new( :hosts => ["broken", "127.0.0.1", "localhost"], - :hosts_shuffle_strategy => Proc.new { |hosts| hosts }, # We do not shuffle for these tests so we always hit the broken host - :network_recovery_interval => 0.2, - :recover_from_connection_close => true), &block) - begin - c.start - block.call(c) - ensure - c.close - end + it "recovers channels (with multiple hosts)" do + with_open_multi_host do |c| + ch1 = c.create_channel + ch2 = c.create_channel + close_all_connections! + poll_until { channels.count.zero? } + poll_until { channels.count == 2 } + expect(ch1).to be_open + expect(ch2).to be_open end + end - def with_recovery_attempts_limited_to(attempts = 3, &block) - c = Bunny.new(:recover_from_connection_close => true, :network_recovery_interval => 0.2, :recovery_attempts => attempts) - begin - c.start - block.call(c) - ensure - c.close - end + it "recovers channels (with multiple hosts, including a broken one)" do + with_open_multi_broken_host do |c| + ch1 = c.create_channel + ch2 = c.create_channel + close_all_connections! + poll_until { channels.count.zero? } + poll_until { channels.count == 2 } + expect(ch1).to be_open + expect(ch2).to be_open end + end - def ensure_queue_recovery(ch, q) - ch.confirm_select - q.purge - x = ch.default_exchange - x.publish("msg", :routing_key => q.name) - ch.wait_for_confirms - sleep 0.5 - expect(q.message_count).to eq 1 - q.purge + it "recovers basic.qos prefetch setting" do + with_open do |c| + ch = c.create_channel + ch.prefetch(11) + expect(ch.prefetch_count).to eq 11 + expect(ch.prefetch_global).to be false + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + expect(ch.prefetch_count).to eq 11 + expect(ch.prefetch_global).to be false end + end - def ensure_queue_binding_recovery(ch, x, q, routing_key = "") - ch.confirm_select - q.purge - x.publish("msg", :routing_key => routing_key) - ch.wait_for_confirms - sleep 0.5 - expect(q.message_count).to eq 1 - q.purge + it "recovers basic.qos prefetch global setting" do + with_open do |c| + ch = c.create_channel + ch.prefetch(42, true) + expect(ch.prefetch_count).to eq 42 + expect(ch.prefetch_global).to be true + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + expect(ch.prefetch_count).to eq 42 + expect(ch.prefetch_global).to be true end + end - def ensure_exchange_binding_recovery(ch, source, destination, routing_key = "") + it "recovers publisher confirms setting" do + with_open do |c| + ch = c.create_channel ch.confirm_select - q = ch.queue("", :exclusive => true) - q.bind(destination, :routing_key => routing_key) - - source.publish("msg", :routing_key => routing_key) - ch.wait_for_confirms - sleep 0.5 - expect(q.message_count).to eq 1 - q.delete + expect(ch).to be_using_publisher_confirms + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + expect(ch).to be_using_publisher_confirms end + end - # - # Examples - # - - it "reconnects after grace period" do - with_open do |c| - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(c).to be_open - end + it "recovers transactionality setting" do + with_open do |c| + ch = c.create_channel + ch.tx_select + expect(ch).to be_using_tx + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + expect(ch).to be_using_tx end + end - it "reconnects after grace period (with multiple hosts)" do - with_open_multi_host do |c| - close_all_connections! - sleep 0.1 - expect(c).not_to be_open + it "recovers client-named queues" do + with_open do |c| + ch = c.create_channel + q = ch.queue("bunny.tests.recovery.client-named#{rand}") + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + ensure_queue_recovery(ch, q) + q.delete + end + end - wait_for_recovery - expect(c).to be_open - end + # a very simplistic test for queues inspired by #412 + it "recovers client-named queues declared with passive = true" do + with_open do |c| + ch = c.create_channel + ch2 = c.create_channel + + n = rand + s = "bunny.tests.recovery.client-named#{n}" + + q = ch.queue(s) + q2 = ch2.queue(s, no_declare: true) + + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + ensure_queue_recovery(ch, q) + q.delete end + end - it "reconnects after grace period (with multiple hosts, including a broken one)" do - with_open_multi_broken_host do |c| - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - wait_for_recovery - expect(c).to be_open - end + it "recovers server-named queues" do + with_open do |c| + ch = c.create_channel + q = ch.queue("", :exclusive => true) + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + ensure_queue_recovery(ch, q) end + end - it "recovers channels" do - with_open do |c| - ch1 = c.create_channel - ch2 = c.create_channel - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch1).to be_open - expect(ch2).to be_open - end + it "recovers queue bindings" do + with_open do |c| + ch = c.create_channel + x = ch.fanout("amq.fanout") + q = ch.queue("", :exclusive => true) + q.bind(x) + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + ensure_queue_binding_recovery(ch, x, q) end + end - it "recovers channels (with multiple hosts)" do - with_open_multi_host do |c| - ch1 = c.create_channel - ch2 = c.create_channel - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch1).to be_open - expect(ch2).to be_open - end - end + it "recovers exchanges and their bindings" do + with_open do |c| + ch = c.create_channel + source = ch.fanout("source.exchange.recovery.example", auto_delete: true) + destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true) - it "recovers channels (with multiple hosts, including a broken one)" do - with_open_multi_broken_host do |c| - ch1 = c.create_channel - ch2 = c.create_channel - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch1).to be_open - expect(ch2).to be_open - end - end + destination.bind(source) - it "recovers basic.qos prefetch setting" do - with_open do |c| - ch = c.create_channel - ch.prefetch(11) - expect(ch.prefetch_count).to eq 11 - expect(ch.prefetch_global).to be false - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - expect(ch.prefetch_count).to eq 11 - expect(ch.prefetch_global).to be false - end - end + # Exchanges won't get auto-deleted on connection loss unless they have + # had an exclusive queue bound to them. + dst_queue = ch.queue("", exclusive: true) + dst_queue.bind(destination, routing_key: "") - it "recovers basic.qos prefetch global setting" do - with_open do |c| - ch = c.create_channel - ch.prefetch(42, true) - expect(ch.prefetch_count).to eq 42 - expect(ch.prefetch_global).to be true - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - expect(ch.prefetch_count).to eq 42 - expect(ch.prefetch_global).to be true - end - end + src_queue = ch.queue("", exclusive: true) + src_queue.bind(source, routing_key: "") - it "recovers publisher confirms setting" do - with_open do |c| - ch = c.create_channel - ch.confirm_select - expect(ch).to be_using_publisher_confirms - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - expect(ch).to be_using_publisher_confirms - end + close_all_connections! + + wait_on_loss_and_recovery_of { exchange_names_in_vhost("/").include?(source.name) } + + ch.confirm_select + + source.publish("msg", routing_key: "") + ch.wait_for_confirms + expect(dst_queue.message_count).to eq 1 end + end - it "recovers transactionality setting" do - with_open do |c| - ch = c.create_channel - ch.tx_select - expect(ch).to be_using_tx - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - expect(ch).to be_using_tx - end + # this is a simplistic test that primarily execises the code path from #412 + it "recovers exchanges that were declared with passive = true" do + with_open do |c| + ch = c.create_channel + ch2 = c.create_channel + source = ch.fanout("source.exchange.recovery.example", auto_delete: true) + destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true) + + source2 = ch2.fanout("source.exchange.recovery.example", no_declare: true) + destination2 = ch2.fanout("destination.exchange.recovery.example", no_declare: true) + + destination.bind(source) + + # Exchanges won't get auto-deleted on connection loss unless they have + # had an exclusive queue bound to them. + dst_queue = ch.queue("", exclusive: true) + dst_queue.bind(destination, routing_key: "") + + src_queue = ch.queue("", exclusive: true) + src_queue.bind(source, routing_key: "") + + close_all_connections! + + wait_on_loss_and_recovery_of { exchange_names_in_vhost("/").include?(source.name) } + + ch2.confirm_select + + source2.publish("msg", routing_key: "") + ch2.wait_for_confirms + expect(dst_queue.message_count).to eq 1 end + end - it "recovers client-named queues" do - with_open do |c| - ch = c.create_channel - q = ch.queue("bunny.tests.recovery.client-named#{rand}") - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - ensure_queue_recovery(ch, q) - q.delete - end + it "recovers allocated channel ids" do + with_open do |c| + q = "queue#{Time.now.to_i}" + 10.times { c.create_channel } + expect(c.queue_exists?(q)).to eq false + close_all_connections! + wait_on_loss_and_recovery_of { channels.any? } + # make sure the connection isn't closed shortly after + # due to "second 'channel.open' seen". MK. + expect(c).to be_open + sleep 0.1 + expect(c).to be_open + sleep 0.1 + expect(c).to be_open end + end + it "recovers consumers" do + with_open do |c| + delivered = false - it "recovers server-named queues" do - with_open do |c| - ch = c.create_channel - q = ch.queue("", :exclusive => true) - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - ensure_queue_recovery(ch, q) + ch = c.create_channel + q = ch.queue("", :exclusive => true) + q.subscribe do |_, _, _| + delivered = true end + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + + q.publish("") + + poll_until { delivered } end + end - it "recovers queue bindings" do - with_open do |c| - ch = c.create_channel - x = ch.fanout("amq.fanout") - q = ch.queue("", :exclusive => true) - q.bind(x) - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - ensure_queue_binding_recovery(ch, x, q) - end + it "recovers all consumers" do + n = 1024 + + with_open do |c| + ch = c.create_channel + q = ch.queue("", :exclusive => true) + n.times { q.subscribe { |_, _, _| } } + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + expect(ch).to be_open + sleep 0.5 + + expect(q.consumer_count).to eq n end + end + + it "recovers all queues" do + n = 256 + + qs = [] + + with_open do |c| + ch = c.create_channel - it "recovers exchange bindings" do - with_open do |c| - ch = c.create_channel - x = ch.fanout("amq.fanout") - x2 = ch.fanout("bunny.tests.recovery.fanout") - x2.bind(x) - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - ensure_exchange_binding_recovery(ch, x, x2) + n.times do + qs << ch.queue("", :exclusive => true) end - end + close_all_connections! + wait_on_loss_and_recovery_of { queue_names.include?(qs.first.name) } + sleep 0.5 + expect(ch).to be_open - it "recovers allocated channel ids" do - with_open do |c| - q = "queue#{Time.now.to_i}" - 10.times { c.create_channel } - expect(c.queue_exists?(q)).to eq false - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(c.queue_exists?(q)).to eq false - # make sure the connection isn't closed shortly after - # due to "second 'channel.open' seen". MK. - expect(c).to be_open - sleep 0.1 - expect(c).to be_open - sleep 0.1 - expect(c).to be_open + qs.each do |q| + ch.queue_declare(q.name, :passive => true) end end + end - it "recovers consumers" do - with_open do |c| - delivered = false - - ch = c.create_channel - q = ch.queue("", :exclusive => true) - q.subscribe do |_, _, _| - delivered = true - end - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - expect(ch).to be_open - - q.publish("") - sleep 0.5 - expect(delivered).to eq true - end + it "tries to recover for a given number of attempts" do + pending "Need a fix for https://github.com/ruby-amqp/bunny/issues/408" + with_recovery_attempts_limited_to(2) do |c| + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + + close_all_connections! + wait_on_loss_and_recovery_of { connections.any? } + + close_all_connections! + sleep(recovery_interval + 0.5) + expect(connections).to be_empty end + end - it "recovers all consumers" do - n = 1024 + def exchange_names_in_vhost(vhost) + http_client.list_exchanges(vhost).map {|e| e["name"]} + end - with_open do |c| - ch = c.create_channel - q = ch.queue("", :exclusive => true) - n.times do - q.subscribe do |_, _, _| - delivered = true - end - end - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - sleep 1 - expect(ch).to be_open + def connections + http_client.list_connections + end - expect(q.consumer_count).to eq n - end + def channels + http_client.list_channels + end + + def queue_names + http_client.list_queues.map {|q| q["name"]} + end + + def close_all_connections! + connections.each do |conn_info| + close_ignoring_permitted_exceptions(conn_info.name) end + end - it "recovers all queues" do - n = 256 + def close_ignoring_permitted_exceptions(connection_name) + http_client.close_connection(connection_name) + rescue Bunny::ConnectionForced + end - qs = [] + def wait_on_loss_and_recovery_of(&probe) + poll_while &probe + poll_until &probe + end - with_open do |c| - ch = c.create_channel + def poll_while(&probe) + Timeout::timeout(10) { + sleep 0.1 while probe[] + } + end - n.times do - qs << ch.queue("", :exclusive => true) - end - close_all_connections! - sleep 0.1 - expect(c).not_to be_open - - wait_for_recovery - sleep 1 - expect(ch).to be_open - - qs.each do |q| - ch.queue_declare(q.name, :passive => true) - end - end - end + def poll_until(&probe) + Timeout::timeout(10) { + sleep 0.1 until probe[] + } + end - it "tries to recover for a given number of attempts" do - with_recovery_attempts_limited_to(2) do |c| - close_all_connections! - expect(c).to receive(:start).exactly(2).times.and_raise(Bunny::TCPConnectionFailed.new("test")) + def with_open(c = Bunny.new(network_recovery_interval: recovery_interval, + recover_from_connection_close: true, + logger: logger), &block) + c.start + block.call(c) + ensure + c.close + end - wait_for_recovery - end - end + def with_open_multi_host(&block) + c = Bunny.new(hosts: ["127.0.0.1", "localhost"], + network_recovery_interval: recovery_interval, + recover_from_connection_close: true, + logger: logger) + with_open(c, &block) + end + + def with_open_multi_broken_host(&block) + c = Bunny.new(hosts: ["broken", "127.0.0.1", "localhost"], + hosts_shuffle_strategy: Proc.new { |hosts| hosts }, # We do not shuffle for these tests so we always hit the broken host + network_recovery_interval: recovery_interval, + recover_from_connection_close: true, + logger: logger) + with_open(c, &block) + end + + def with_recovery_attempts_limited_to(attempts = 3, &block) + c = Bunny.new(recover_from_connection_close: true, + network_recovery_interval: recovery_interval, + recovery_attempts: attempts, + logger: logger) + with_open(c, &block) + end + + def ensure_queue_recovery(ch, q) + ch.confirm_select + q.purge + x = ch.default_exchange + x.publish("msg", routing_key: q.name) + ch.wait_for_confirms + expect(q.message_count).to eq 1 + q.purge + end + + def ensure_queue_binding_recovery(ch, x, q, routing_key = "") + ch.confirm_select + q.purge + x.publish("msg", routing_key: routing_key) + ch.wait_for_confirms + expect(q.message_count).to eq 1 + q.purge end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/higher_level_api/integration/publisher_confirms_spec.rb new/spec/higher_level_api/integration/publisher_confirms_spec.rb --- old/spec/higher_level_api/integration/publisher_confirms_spec.rb 2016-06-11 23:24:31.000000000 +0200 +++ new/spec/higher_level_api/integration/publisher_confirms_spec.rb 2016-07-20 13:34:54.000000000 +0200 @@ -60,6 +60,18 @@ }.not_to raise_error end + + it "raises an error when called on a closed channel" do + ch = connection.create_channel + + ch.confirm_select + + ch.close + + expect { + ch.wait_for_confirms + }.to raise_error(Bunny::ChannelAlreadyClosed) + end end context "when some of the messages get nacked" do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/unit/exchange_recovery_spec.rb new/spec/unit/exchange_recovery_spec.rb --- old/spec/unit/exchange_recovery_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/spec/unit/exchange_recovery_spec.rb 2016-07-20 13:34:54.000000000 +0200 @@ -0,0 +1,39 @@ +require_relative '../../lib/bunny/channel' +require_relative '../../lib/bunny/exchange' + +module Bunny + describe Exchange do + context "recovery" do + it "recovers exchange bindings, unless already unbound" do + ch = instance_double(Bunny::Channel, + exchange_declare: nil, + register_exchange: nil) + src1 = Exchange.new(ch, "direct", "src1") + src2 = Exchange.new(ch, "direct", "src2") + src3 = Exchange.new(ch, "direct", "src3") + dst = Exchange.new(ch, "direct", "dst") + + original_binds_count = 5 + expected_rebinds_count = 3 + expected_total_binds = original_binds_count + expected_rebinds_count + allow(ch).to receive(:exchange_bind).exactly(expected_total_binds).times + + dst.bind(src1, routing_key: "abc") + dst.bind(src2, routing_key: "def") + dst.bind(src2, routing_key: "ghi") + dst.bind(src3, routing_key: "jkl") + dst.bind(src3, routing_key: "jkl", arguments: {"key" => "value"}) + + allow(ch).to receive(:exchange_unbind).twice + dst.unbind(src2, routing_key: "def") + dst.unbind(src3, routing_key: "jkl", arguments: {"key" => "value"}) + + expect(ch).to receive(:exchange_bind).with(src1, dst, routing_key: "abc") + expect(ch).to receive(:exchange_bind).with(src2, dst, routing_key: "ghi") + expect(ch).to receive(:exchange_bind).with(src3, dst, routing_key: "jkl") + + dst.recover_from_network_failure + end + end + end +end