bunny 1.0.0.pre3 → 1.0.0.pre4

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -1
  3. data/ChangeLog.md +54 -0
  4. data/README.md +5 -5
  5. data/benchmarks/mutex_and_monitor.rb +42 -0
  6. data/bunny.gemspec +2 -2
  7. data/examples/guides/extensions/connection_blocked.rb +35 -0
  8. data/lib/bunny/channel.rb +16 -7
  9. data/lib/bunny/exceptions.rb +4 -1
  10. data/lib/bunny/reader_loop.rb +17 -3
  11. data/lib/bunny/session.rb +58 -6
  12. data/lib/bunny/transport.rb +10 -1
  13. data/lib/bunny/version.rb +1 -1
  14. data/spec/higher_level_api/integration/basic_cancel_spec.rb +1 -1
  15. data/spec/higher_level_api/integration/basic_consume_spec.rb +1 -1
  16. data/spec/higher_level_api/integration/basic_nack_spec.rb +1 -1
  17. data/spec/higher_level_api/integration/basic_publish_spec.rb +1 -1
  18. data/spec/higher_level_api/integration/basic_qos_spec.rb +5 -8
  19. data/spec/higher_level_api/integration/basic_reject_spec.rb +16 -17
  20. data/spec/higher_level_api/integration/basic_return_spec.rb +1 -1
  21. data/spec/higher_level_api/integration/channel_close_spec.rb +6 -10
  22. data/spec/higher_level_api/integration/channel_flow_spec.rb +6 -9
  23. data/spec/higher_level_api/integration/channel_open_spec.rb +11 -20
  24. data/spec/higher_level_api/integration/confirm_select_spec.rb +1 -1
  25. data/spec/higher_level_api/integration/connection_spec.rb +1 -1
  26. data/spec/higher_level_api/integration/connection_stop_spec.rb +13 -0
  27. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +1 -1
  28. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +46 -1
  29. data/spec/higher_level_api/integration/dead_lettering_spec.rb +1 -1
  30. data/spec/higher_level_api/integration/exchange_bind_spec.rb +1 -1
  31. data/spec/higher_level_api/integration/exchange_declare_spec.rb +1 -1
  32. data/spec/higher_level_api/integration/exchange_delete_spec.rb +1 -1
  33. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +1 -1
  34. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +28 -0
  35. data/spec/higher_level_api/integration/merry_go_round_spec.rb +1 -1
  36. data/spec/higher_level_api/integration/message_properties_access_spec.rb +1 -1
  37. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +1 -1
  38. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +1 -1
  39. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +1 -1
  40. data/spec/higher_level_api/integration/queue_declare_spec.rb +1 -1
  41. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  42. data/spec/higher_level_api/integration/queue_purge_spec.rb +1 -1
  43. data/spec/higher_level_api/integration/queue_unbind_spec.rb +2 -2
  44. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +1 -1
  45. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +2 -2
  46. data/spec/higher_level_api/integration/tls_connection_spec.rb +2 -2
  47. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  48. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  49. data/spec/stress/connection_open_close_spec.rb +25 -2
  50. data/spec/unit/concurrent/condition_spec.rb +53 -46
  51. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +48 -9
  52. metadata +10 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 42830e201dcbd08b5367454288ce79c6924ea0d4
4
- data.tar.gz: 04005efee192cb86d4e7af17776e53e13712525b
3
+ metadata.gz: c1668fc7a45c060f60a853a68eab96a4f3a18b56
4
+ data.tar.gz: 36139657c1d1333fbc97cf070d588e4fe8bd2927
5
5
  SHA512:
6
- metadata.gz: 6d1841d225856610228745312a60ae7e86cd175a40528902f2834aa1c51c548be68a74a481717127f8d4d15463670a4a7a80577983b7766ff841bcb51822a37c
7
- data.tar.gz: 44dbd529f0b0d16ae0f3c115040a9b6879f85ef7e4da6668dbe869a10e5e2b752232828eec30b70292b1fd582e0876a8f9eeb420c81e6d05eb1ac22f9d1c8198
6
+ metadata.gz: dde418ea8694ed3c7755552d8f8eb43ca3afe3d8063ecf05e1fc359b8705fa6ef8c148a463d65426bbfa1dafea14979a4b0e9439296b106684b33dd590bf0d02
7
+ data.tar.gz: b825a7c0f8e3e0cdf1a44f46a4d6eac297a6f356f9e4ec153b3dc4301a499923eccf22e26c9a4079502d428f9a32887378f86669cd33cd9091e0597c00d9a639
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  bundler_args: --without development
2
2
  before_script: "./bin/ci/before_build.sh"
3
- script: "bundle exec rspec -c spec"
3
+ script: "bundle exec rspec -cfs spec"
4
4
  rvm:
5
5
  - "2.0"
6
6
  - "1.9.3"
@@ -15,3 +15,6 @@ branches:
15
15
  only:
16
16
  - master
17
17
  - 0.9.x-stable
18
+ matrix:
19
+ allow_failures:
20
+ - rvm: rbx-19mode
data/ChangeLog.md CHANGED
@@ -1,3 +1,57 @@
1
+ ## Changes between Bunny 1.0.0.pre3 and 1.0.0.pre4
2
+
3
+ ### Default Paths for TLS/SSL CA's on Linux
4
+
5
+ Bunny now will use the following TLS/SSL CA's paths on Linux by default:
6
+
7
+ * `/etc/ssl/certs/ca-certificates.crt` on Ubuntu/Debian
8
+ * `/etc/ssl/certs/ca-bundle.crt` on Amazon Linux
9
+ * `/etc/ssl/ca-bundle.pem` on OpenSUSE
10
+ * `/etc/pki/tls/certs/ca-bundle.crt` on Fedora/RHEL
11
+
12
+ and will log a warning if no CA files are available via default paths
13
+ or `:tls_ca_certificates`.
14
+
15
+ Contributed by Carl Hörberg.
16
+
17
+ ### Consumers Can Be Re-Registered From Bunny::Consumer#handle_cancellation
18
+
19
+ It is now possible to re-register a consumer (and use any other synchronous methods)
20
+ from `Bunny::Consumer#handle_cancellation`, which is now invoked in the channel's
21
+ thread pool.
22
+
23
+
24
+ ### Bunny::Session#close Fixed for Single Threaded Connections
25
+
26
+ `Bunny::Session#close` with single threaded connections no longer fails
27
+ with a nil pointer exception.
28
+
29
+
30
+
31
+ ## Changes between Bunny 1.0.0.pre2 and 1.0.0.pre3
32
+
33
+ This release has **breaking API changes**.
34
+
35
+ ### Safe[r] basic.ack, basic.nack and basic.reject implementation
36
+
37
+ Previously if a channel was recovered (reopened) by automatic connection
38
+ recovery before a message was acknowledged or rejected, it would cause
39
+ any operation on the channel that uses delivery tags to fail and
40
+ cause the channel to be closed.
41
+
42
+ To avoid this issue, every channel keeps a counter of how many times
43
+ it has been reopened and marks delivery tags with them. Using a stale
44
+ tag to ack or reject a message will produce no method sent to RabbitMQ.
45
+ Note that unacknowledged messages will be requeued by RabbitMQ when connection
46
+ goes down anyway.
47
+
48
+ This involves an API change: `Bunny::DeliveryMetadata#delivery_tag` is now
49
+ and instance of a class that responds to `#tag` and `#to_i` and is accepted
50
+ by `Bunny::Channel#ack` and related methods.
51
+
52
+ Integers are still accepted by the same methods.
53
+
54
+
1
55
  ## Changes between Bunny 1.0.0.pre1 and 1.0.0.pre2
2
56
 
3
57
  ### Exclusivity Violation for Consumers Now Raises a Reasonable Exception
data/README.md CHANGED
@@ -79,7 +79,7 @@ backwards compatible as possible but within reason.
79
79
 
80
80
  ### With Rubygems
81
81
 
82
- To install Bunny 0.9.x with RubyGems:
82
+ To install Bunny with RubyGems:
83
83
 
84
84
  ```
85
85
  gem install bunny
@@ -87,17 +87,17 @@ gem install bunny
87
87
 
88
88
  ### Bundler Dependency
89
89
 
90
- To use Bunny 0.9.x in a project managed with Bundler:
90
+ To use Bunny in a project managed with Bundler:
91
91
 
92
92
  ``` ruby
93
- gem "bunny", ">= 0.9.3"
93
+ gem "bunny", ">= 0.10.0"
94
94
  ```
95
95
 
96
96
 
97
- ## Quick Start for Bunny 0.9.x
97
+ ## Quick Start
98
98
 
99
99
  Below is a small snippet that demonstrates how to publish
100
- and synchronously consume ("pull API") messages with Bunny 0.9.
100
+ and synchronously consume ("pull API") messages with Bunny.
101
101
 
102
102
  For a 15 minute tutorial using more practical examples, see [Getting Started with RabbitMQ and Ruby using Bunny](http://rubybunny.info/articles/getting_started.html).
103
103
 
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "rubygems"
5
+ require "set"
6
+ require "thread"
7
+ require "benchmark"
8
+ require "monitor"
9
+
10
+ puts
11
+ puts "-" * 80
12
+ puts "Benchmarking on #{RUBY_DESCRIPTION}"
13
+
14
+ n = 2_000_000
15
+ mx = Mutex.new
16
+ mt = Monitor.new
17
+
18
+ # warm up the JIT, etc
19
+ puts "Doing a warmup run..."
20
+ n.times do |i|
21
+ mx.synchronize { 1 }
22
+ mt.synchronize { 1 }
23
+ end
24
+
25
+ t1 = Benchmark.realtime do
26
+ n.times do |i|
27
+ mx.synchronize { 1 }
28
+ end
29
+ end
30
+ r1 = (n.to_f/t1.to_f)
31
+
32
+ t2 = Benchmark.realtime do
33
+ n.times do |i|
34
+ mt.synchronize { 1 }
35
+ end
36
+ end
37
+ r2 = (n.to_f/t2.to_f)
38
+
39
+ puts "Mutex#synchronize, rate: #{(r1 / 1000).round(2)} KGHz"
40
+ puts "Monitor#synchronize, rate: #{(r2 / 1000).round(2)} KGHz"
41
+ puts
42
+ puts "-" * 80
data/bunny.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.version = Bunny::VERSION.dup
10
10
  s.homepage = "http://rubybunny.info"
11
11
  s.summary = "Popular easy to use Ruby client for RabbitMQ"
12
- s.description = "Easy to use, feature complete Ruby client for RabbitMQ 2.0."
12
+ s.description = "Easy to use, feature complete Ruby client for RabbitMQ 2.0 and later versions."
13
13
  s.license = "MIT"
14
14
 
15
15
  # Sorted alphabetically.
@@ -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.6.0"
32
+ s.add_dependency "amq-protocol", ">= 1.7.0"
33
33
 
34
34
  # Files.
35
35
  s.has_rdoc = true
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "rubygems"
5
+ require "bunny"
6
+
7
+ puts "=> Demonstrating connection.blocked"
8
+ puts
9
+
10
+ conn = Bunny.new
11
+ conn.start
12
+
13
+ ch = conn.create_channel
14
+ x = ch.fanout("amq.fanout")
15
+
16
+ # This example requires high memory watermark to be set
17
+ # really low to demonstrate blocking.
18
+ #
19
+ # rabbitmqctl set_vm_memory_high_watermark 0.00000001
20
+ #
21
+ # should do it.
22
+
23
+ conn.on_blocked do |connection_blocked|
24
+ puts "Connection is blocked. Reason: #{connection_blocked.reason}"
25
+ end
26
+
27
+ conn.on_unblocked do |connection_unblocked|
28
+ puts "Connection is unblocked."
29
+ end
30
+
31
+ x.publish("z" * 1024 * 1024 * 16)
32
+
33
+ sleep 120.0
34
+ puts "Disconnecting..."
35
+ conn.close
data/lib/bunny/channel.rb CHANGED
@@ -843,6 +843,7 @@ module Bunny
843
843
 
844
844
  @last_basic_consume_ok
845
845
  end
846
+ alias consume basic_consume
846
847
 
847
848
  # Registers a consumer for queue as {Bunny::Consumer} instance.
848
849
  #
@@ -894,6 +895,7 @@ module Bunny
894
895
 
895
896
  @last_basic_consume_ok
896
897
  end
898
+ alias consume_with basic_consume_with
897
899
 
898
900
  # Removes a consumer. Messages for this consumer will no longer be delivered. If the queue
899
901
  # it was on is auto-deleted and this consumer was the last one, the queue will be deleted.
@@ -1538,10 +1540,17 @@ module Bunny
1538
1540
  @continuations.push(method)
1539
1541
  when AMQ::Protocol::Basic::Cancel then
1540
1542
  if consumer = @consumers[method.consumer_tag]
1541
- consumer.handle_cancellation(method)
1543
+ @work_pool.submit do
1544
+ begin
1545
+ @consumers.delete(method.consumer_tag)
1546
+ consumer.handle_cancellation(method)
1547
+ rescue Exception => e
1548
+ @logger.error "Got excepton when notifying consumer #{method.consumer_tag} about cancellation!"
1549
+ end
1550
+ end
1551
+ else
1552
+ @logger.warn "No consumer for tag #{method.consumer_tag} on channel #{@id}!"
1542
1553
  end
1543
-
1544
- @consumers.delete(method.consumer_tag)
1545
1554
  when AMQ::Protocol::Basic::CancelOk then
1546
1555
  @continuations.push(method)
1547
1556
  unregister_consumer(method.consumer_tag)
@@ -1847,12 +1856,12 @@ module Bunny
1847
1856
  # @private
1848
1857
  def guarding_against_stale_delivery_tags(tag, &block)
1849
1858
  case tag
1850
- # if a fixnum was passed, execute unconditionally. MK.
1859
+ # if a fixnum was passed, execute unconditionally. MK.
1851
1860
  when Fixnum then
1852
1861
  block.call
1853
- # versioned delivery tags should be checked to avoid
1854
- # sending out stale (invalid) tags after channel was reopened
1855
- # during network failure recovery. MK.
1862
+ # versioned delivery tags should be checked to avoid
1863
+ # sending out stale (invalid) tags after channel was reopened
1864
+ # during network failure recovery. MK.
1856
1865
  when VersionedDeliveryTag then
1857
1866
  if !tag.stale?(@recoveries_counter.get)
1858
1867
  block.call
@@ -55,7 +55,7 @@ module Bunny
55
55
  when Exception then
56
56
  e.message
57
57
  end
58
- super("Could not estabilish TCP connection to #{hostname}:#{port}: #{m}")
58
+ super("Could not establish TCP connection to #{hostname}:#{port}: #{m}")
59
59
  end
60
60
  end
61
61
 
@@ -70,6 +70,9 @@ module Bunny
70
70
  end
71
71
  end
72
72
 
73
+ class ShutdownSignal < Exception
74
+ end
75
+
73
76
  # Raised when RabbitMQ closes TCP connection before finishing connection
74
77
  # sequence properly. This typically indicates an authentication issue.
75
78
  class PossibleAuthenticationFailureError < Exception
@@ -19,7 +19,6 @@ module Bunny
19
19
 
20
20
  def start
21
21
  @thread = Thread.new(&method(:run_loop))
22
- @thread.abort_on_exception = true
23
22
  end
24
23
 
25
24
  def resume
@@ -33,8 +32,10 @@ module Bunny
33
32
  break if @stopping || @network_is_down
34
33
  run_once
35
34
  rescue Errno::EBADF => ebadf
35
+ break if @stopping
36
36
  # ignored, happens when we loop after the transport has already been closed
37
37
  rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError => e
38
+ break if @stopping
38
39
  log_exception(e)
39
40
 
40
41
  @network_is_down = true
@@ -44,7 +45,10 @@ module Bunny
44
45
  else
45
46
  @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
46
47
  end
48
+ rescue ShutdownSignal => _
49
+ break
47
50
  rescue Exception => e
51
+ break if @stopping
48
52
  log_exception(e)
49
53
 
50
54
  @network_is_down = true
@@ -86,9 +90,19 @@ module Bunny
86
90
  @stopped
87
91
  end
88
92
 
93
+ def raise(e)
94
+ @thread.raise(e) if @thread
95
+ end
96
+
97
+ def join
98
+ @thread.join if @thread
99
+ end
100
+
89
101
  def kill
90
- @thread.kill
91
- @thread.join
102
+ if @thread
103
+ @thread.kill
104
+ @thread.join
105
+ end
92
106
  end
93
107
 
94
108
  def log_exception(e)
data/lib/bunny/session.rb CHANGED
@@ -54,7 +54,8 @@ module Bunny
54
54
  :publisher_confirms => true,
55
55
  :consumer_cancel_notify => true,
56
56
  :exchange_exchange_bindings => true,
57
- :"basic.nack" => true
57
+ :"basic.nack" => true,
58
+ :"connection.blocked" => true
58
59
  },
59
60
  :product => "Bunny",
60
61
  :platform => ::RUBY_DESCRIPTION,
@@ -135,6 +136,7 @@ module Bunny
135
136
  @continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT)
136
137
 
137
138
  @status = :not_connected
139
+ @blocked = false
138
140
 
139
141
  # these are negotiated with the broker during the connection tuning phase
140
142
  @client_frame_max = opts.fetch(:frame_max, DEFAULT_FRAME_MAX)
@@ -208,6 +210,9 @@ module Bunny
208
210
  return self if connected?
209
211
 
210
212
  @status = :connecting
213
+ # reset here for cases when automatic network recovery kicks in
214
+ # when we were blocked. MK.
215
+ @blocked = false
211
216
  self.reset_continuations
212
217
 
213
218
  begin
@@ -226,7 +231,7 @@ module Bunny
226
231
  self.open_connection
227
232
 
228
233
  @reader_loop = nil
229
- self.start_reader_loop if @threaded
234
+ self.start_reader_loop if threaded?
230
235
 
231
236
  @default_channel = self.create_channel
232
237
  rescue Exception => e
@@ -342,6 +347,34 @@ module Bunny
342
347
  @default_channel.exchange(*args)
343
348
  end
344
349
 
350
+ # Defines a callback that will be executed when RabbitMQ blocks the connection
351
+ # because it is running low on memory or disk space (as configured via config file
352
+ # and/or rabbitmqctl).
353
+ #
354
+ # @yield [AMQ::Protocol::Connection::Blocked] connection.blocked method which provides a reason for blocking
355
+ #
356
+ # @api public
357
+ def on_blocked(&block)
358
+ @block_callback = block
359
+ end
360
+
361
+ # Defines a callback that will be executed when RabbitMQ unblocks the connection
362
+ # that was previously blocked, e.g. because the memory or disk space alarm has cleared.
363
+ #
364
+ # @see #on_blocked
365
+ # @api public
366
+ def on_unblocked(&block)
367
+ @unblock_callback = block
368
+ end
369
+
370
+ # @return [Boolean] true if the connection is currently blocked by RabbitMQ because it's running low on
371
+ # RAM, disk space, or other resource; false otherwise
372
+ # @see #on_blocked
373
+ # @see #on_unblocked
374
+ def blocked?
375
+ @blocked
376
+ end
377
+
345
378
 
346
379
  #
347
380
  # Implementation
@@ -418,6 +451,12 @@ module Bunny
418
451
  ensure
419
452
  @continuations.push(:__unblock__)
420
453
  end
454
+ when AMQ::Protocol::Connection::Blocked then
455
+ @blocked = true
456
+ @block_callback.call(method) if @block_callback
457
+ when AMQ::Protocol::Connection::Unblocked then
458
+ @blocked = false
459
+ @unblock_callback.call(method) if @unblock_callback
421
460
  when AMQ::Protocol::Channel::Close then
422
461
  begin
423
462
  ch = @channels[ch_number]
@@ -626,10 +665,23 @@ module Bunny
626
665
  def maybe_shutdown_reader_loop
627
666
  if @reader_loop
628
667
  @reader_loop.stop
629
- # We don't need to kill the loop but
630
- # this is the easiest way to wait until the loop
631
- # is guaranteed to have terminated
632
- @reader_loop.kill
668
+ if threaded?
669
+ # this is the easiest way to wait until the loop
670
+ # is guaranteed to have terminated
671
+ @reader_loop.raise(ShutdownSignal)
672
+ # joining the thread here may take forever
673
+ # on JRuby because sun.nio.ch.KQueueArrayWrapper#kevent0 is
674
+ # a native method that cannot be (easily) interrupted.
675
+ # So we use this ugly hack or else our test suite takes forever
676
+ # to run on JRuby (a new connection is opened/closed per example). MK.
677
+ if RUBY_ENGINE == "jruby"
678
+ sleep 0.075
679
+ else
680
+ @reader_loop.join
681
+ end
682
+ else
683
+ # single threaded mode, nothing to do. MK.
684
+ end
633
685
  end
634
686
 
635
687
  @reader_loop = nil