bunny 1.0.0.pre3 → 1.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
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