bunny 0.8.0 → 0.9.0.pre1

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 (91) hide show
  1. data/.gitignore +7 -1
  2. data/.travis.yml +14 -4
  3. data/ChangeLog.md +72 -0
  4. data/Gemfile +17 -11
  5. data/README.md +82 -0
  6. data/bunny.gemspec +6 -13
  7. data/examples/connection/heartbeat.rb +17 -0
  8. data/lib/bunny.rb +40 -56
  9. data/lib/bunny/channel.rb +615 -19
  10. data/lib/bunny/channel_id_allocator.rb +59 -0
  11. data/lib/bunny/compatibility.rb +24 -0
  12. data/lib/bunny/concurrent/condition.rb +63 -0
  13. data/lib/bunny/consumer.rb +42 -26
  14. data/lib/bunny/consumer_tag_generator.rb +22 -0
  15. data/lib/bunny/consumer_work_pool.rb +67 -0
  16. data/lib/bunny/exceptions.rb +128 -0
  17. data/lib/bunny/exchange.rb +131 -136
  18. data/lib/bunny/framing.rb +53 -0
  19. data/lib/bunny/heartbeat_sender.rb +59 -0
  20. data/lib/bunny/main_loop.rb +70 -0
  21. data/lib/bunny/message_metadata.rb +126 -0
  22. data/lib/bunny/queue.rb +102 -275
  23. data/lib/bunny/session.rb +478 -0
  24. data/lib/bunny/socket.rb +44 -0
  25. data/lib/bunny/system_timer.rb +9 -9
  26. data/lib/bunny/transport.rb +179 -0
  27. data/lib/bunny/version.rb +1 -1
  28. data/spec/compatibility/queue_declare_spec.rb +40 -0
  29. data/spec/higher_level_api/integration/basic_ack_spec.rb +54 -0
  30. data/spec/higher_level_api/integration/basic_consume_spec.rb +51 -0
  31. data/spec/higher_level_api/integration/basic_get_spec.rb +47 -0
  32. data/spec/higher_level_api/integration/basic_nack_spec.rb +39 -0
  33. data/spec/higher_level_api/integration/basic_publish_spec.rb +105 -0
  34. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -0
  35. data/spec/higher_level_api/integration/basic_recover_spec.rb +18 -0
  36. data/spec/higher_level_api/integration/basic_reject_spec.rb +53 -0
  37. data/spec/higher_level_api/integration/basic_return_spec.rb +33 -0
  38. data/spec/higher_level_api/integration/channel_close_spec.rb +29 -0
  39. data/spec/higher_level_api/integration/channel_flow_spec.rb +24 -0
  40. data/spec/higher_level_api/integration/channel_open_spec.rb +57 -0
  41. data/spec/higher_level_api/integration/channel_open_stress_spec.rb +22 -0
  42. data/spec/higher_level_api/integration/confirm_select_spec.rb +19 -0
  43. data/spec/higher_level_api/integration/connection_spec.rb +340 -0
  44. data/spec/higher_level_api/integration/exchange_bind_spec.rb +31 -0
  45. data/spec/higher_level_api/integration/exchange_declare_spec.rb +183 -0
  46. data/spec/higher_level_api/integration/exchange_delete_spec.rb +37 -0
  47. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +40 -0
  48. data/spec/higher_level_api/integration/queue_bind_spec.rb +109 -0
  49. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -0
  50. data/spec/higher_level_api/integration/queue_delete_spec.rb +38 -0
  51. data/spec/higher_level_api/integration/queue_purge_spec.rb +30 -0
  52. data/spec/higher_level_api/integration/queue_unbind_spec.rb +33 -0
  53. data/spec/higher_level_api/integration/tx_commit_spec.rb +21 -0
  54. data/spec/higher_level_api/integration/tx_rollback_spec.rb +21 -0
  55. data/spec/lower_level_api/integration/basic_cancel_spec.rb +57 -0
  56. data/spec/lower_level_api/integration/basic_consume_spec.rb +100 -0
  57. data/spec/spec_helper.rb +64 -0
  58. data/spec/unit/bunny_spec.rb +15 -0
  59. data/spec/unit/concurrent/condition_spec.rb +66 -0
  60. metadata +135 -93
  61. data/CHANGELOG +0 -21
  62. data/README.textile +0 -76
  63. data/Rakefile +0 -14
  64. data/examples/simple.rb +0 -32
  65. data/examples/simple_ack.rb +0 -35
  66. data/examples/simple_consumer.rb +0 -55
  67. data/examples/simple_fanout.rb +0 -41
  68. data/examples/simple_headers.rb +0 -42
  69. data/examples/simple_publisher.rb +0 -29
  70. data/examples/simple_topic.rb +0 -61
  71. data/ext/amqp-0.9.1.json +0 -389
  72. data/ext/config.yml +0 -4
  73. data/ext/qparser.rb +0 -426
  74. data/lib/bunny/client.rb +0 -370
  75. data/lib/bunny/subscription.rb +0 -92
  76. data/lib/qrack/amq-client-url.rb +0 -165
  77. data/lib/qrack/channel.rb +0 -20
  78. data/lib/qrack/client.rb +0 -247
  79. data/lib/qrack/errors.rb +0 -5
  80. data/lib/qrack/protocol/protocol.rb +0 -135
  81. data/lib/qrack/protocol/spec.rb +0 -525
  82. data/lib/qrack/qrack.rb +0 -20
  83. data/lib/qrack/queue.rb +0 -40
  84. data/lib/qrack/subscription.rb +0 -152
  85. data/lib/qrack/transport/buffer.rb +0 -305
  86. data/lib/qrack/transport/frame.rb +0 -102
  87. data/spec/spec_09/amqp_url_spec.rb +0 -19
  88. data/spec/spec_09/bunny_spec.rb +0 -76
  89. data/spec/spec_09/connection_spec.rb +0 -34
  90. data/spec/spec_09/exchange_spec.rb +0 -173
  91. data/spec/spec_09/queue_spec.rb +0 -240
@@ -0,0 +1,59 @@
1
+ require "thread"
2
+
3
+ module Bunny
4
+ class ChannelIdAllocator
5
+
6
+ #
7
+ # API
8
+ #
9
+
10
+ def initialize
11
+ max_channel = (1 << 16) - 1
12
+ @int_allocator ||= AMQ::IntAllocator.new(1, max_channel)
13
+
14
+ @channel_id_mutex ||= Mutex.new
15
+ end
16
+
17
+
18
+ # Returns next available channel id. This method is thread safe.
19
+ #
20
+ # @return [Fixnum]
21
+ # @api public
22
+ # @see ChannelManager#release_channel_id
23
+ # @see ChannelManager#reset_channel_id_allocator
24
+ def next_channel_id
25
+ @channel_id_mutex.synchronize do
26
+ @int_allocator.allocate
27
+ end
28
+ end
29
+
30
+ # Releases previously allocated channel id. This method is thread safe.
31
+ #
32
+ # @param [Fixnum] Channel id to release
33
+ # @api public
34
+ # @see ChannelManager#next_channel_id
35
+ # @see ChannelManager#reset_channel_id_allocator
36
+ def release_channel_id(i)
37
+ @channel_id_mutex.synchronize do
38
+ @int_allocator.release(i)
39
+ end
40
+ end # self.release_channel_id(i)
41
+
42
+
43
+ def allocated_channel_id?(i)
44
+ @channel_id_mutex.synchronize do
45
+ @int_allocator.allocated?(i)
46
+ end
47
+ end
48
+
49
+ # Resets channel allocator. This method is thread safe.
50
+ # @api public
51
+ # @see Channel.next_channel_id
52
+ # @see Channel.release_channel_id
53
+ def reset_channel_id_allocator
54
+ @channel_id_mutex.synchronize do
55
+ @int_allocator.reset
56
+ end
57
+ end # reset_channel_id_allocator
58
+ end
59
+ end
@@ -0,0 +1,24 @@
1
+ module Bunny
2
+ # Helper methods necessary to stay mostly backwards-compatible with legacy (0.7.x, 0.8.x) Bunny
3
+ # releases that hide channels completely from the API.
4
+ #
5
+ # @private
6
+ module Compatibility
7
+
8
+ #
9
+ # API
10
+ #
11
+
12
+ # @api public
13
+ def channel_from(channel_or_connection)
14
+ # Bunny 0.8.x and earlier completely hide channels from the API. So, queues and exchanges are
15
+ # instantiated with a "Bunny object", which is a session. This function coerces two types of input to a
16
+ # channel.
17
+ if channel_or_connection.is_a?(Bunny::Session)
18
+ channel_or_connection.default_channel
19
+ else
20
+ channel_or_connection
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ require "thread"
2
+
3
+ module Bunny
4
+ module Concurrent
5
+ # Akin to java.util.concurrent.Condition and intrinsic object monitors (Object#wait, Object#notify, Object#notifyAll) in Java:
6
+ # threads can wait (block until notified) on a condition other threads notify them about.
7
+ # Unlike the j.u.c. version, this one has a single waiting set.
8
+ #
9
+ # Conditions can optionally be annotated with a description string for ease of debugging.
10
+ class Condition
11
+ attr_reader :waiting_threads, :description
12
+
13
+
14
+ def initialize(description = nil)
15
+ @mutex = Mutex.new
16
+ @waiting_threads = []
17
+ @description = description
18
+ end
19
+
20
+ def wait
21
+ @mutex.synchronize do
22
+ t = Thread.current
23
+ @waiting_threads.push(t)
24
+ end
25
+
26
+ Thread.stop
27
+ end
28
+
29
+ def notify
30
+ @mutex.synchronize do
31
+ t = @waiting_threads.shift
32
+ begin
33
+ t.run if t
34
+ rescue ThreadError
35
+ retry
36
+ end
37
+ end
38
+ end
39
+
40
+ def notify_all
41
+ @mutex.synchronize do
42
+ @waiting_threads.each do |t|
43
+ t.run
44
+ end
45
+
46
+ @waiting_threads.clear
47
+ end
48
+ end
49
+
50
+ def waiting_set_size
51
+ @mutex.synchronize { @waiting_threads.size }
52
+ end
53
+
54
+ def any_threads_waiting?
55
+ @mutex.synchronize { !@waiting_threads.empty? }
56
+ end
57
+
58
+ def none_threads_waiting?
59
+ @mutex.synchronize { @waiting_threads.empty? }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,35 +1,51 @@
1
- # encoding: utf-8
1
+ module Bunny
2
+ class Consumer
2
3
 
3
- ####################################
4
- # NOTE: THIS CLASS IS HERE TO MAKE #
5
- # TRANSITION TO AMQ CLIENT EASIER #
6
- ####################################
4
+ #
5
+ # API
6
+ #
7
7
 
8
- require "qrack/subscription"
8
+ attr_reader :channel
9
+ attr_reader :queue
10
+ attr_reader :consumer_tag
11
+ attr_reader :arguments
9
12
 
10
- # NOTE: This file is rather a temporary hack to fix
11
- # https://github.com/ruby-amqp/bunny/issues/9 then
12
- # some permanent solution. It's mostly copied from
13
- # the AMQP and AMQ Client gems. Later on we should
14
- # use AMQ Client directly and just inherit from
15
- # the AMQ::Client::Sync::Consumer class.
16
13
 
17
- module Bunny
14
+ def initialize(channel, queue, consumer_tag, no_ack = false, exclusive = false, arguments = {})
15
+ @channel = channel || raise(ArgumentError, "channel is nil")
16
+ @queue = queue || raise(ArgumentError, "queue is nil")
17
+ @consumer_tag = consumer_tag || raise(ArgumentError, "consumer tag is nil")
18
+ @exclusive = exclusive
19
+ @arguments = arguments
20
+ @no_ack = no_ack
21
+ end
22
+
23
+
24
+ def on_delivery(&block)
25
+ @on_delivery = block
26
+ self
27
+ end
18
28
 
19
- # AMQP consumers are entities that handle messages delivered
20
- # to them ("push API" as opposed to "pull API") by AMQP broker.
21
- # Every consumer is associated with a queue. Consumers can be
22
- # exclusive (no other consumers can be registered for the same
23
- # queue) or not (consumers share the queue). In the case of
24
- # multiple consumers per queue, messages are distributed in
25
- # round robin manner with respect to channel-level prefetch
26
- # setting).
27
- class Consumer < Qrack::Subscription
28
- def initialize(*args)
29
- super(*args)
30
- @consumer_tag ||= (1..32).to_a.shuffle.join
29
+ def call(*args)
30
+ @on_delivery.call(*args) if @on_delivery
31
31
  end
32
+ alias handle_delivery call
32
33
 
33
- alias_method :consume, :start
34
+ def handle_cancel(&block)
35
+ @on_cancellation = block
36
+ self
37
+ end
38
+
39
+ def queue_name
40
+ if @queue.respond_to?(:name)
41
+ @queue.name
42
+ else
43
+ @queue
44
+ end
45
+ end
46
+
47
+ def inspect
48
+ "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag} @exclusive=#{@exclusive} @no_ack=#{@no_ack}>"
49
+ end
34
50
  end
35
51
  end
@@ -0,0 +1,22 @@
1
+ module Bunny
2
+ class ConsumerTagGenerator
3
+
4
+ #
5
+ # API
6
+ #
7
+
8
+ # @return [String] Generated consumer tag
9
+ def generate
10
+ "#{Kernel.rand}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
11
+ end # generate
12
+
13
+
14
+ # Unique string supposed to be used as a consumer tag.
15
+ #
16
+ # @return [String] Unique string.
17
+ # @api public
18
+ def generate_prefixed(name = "bunny")
19
+ "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,67 @@
1
+ require "thread"
2
+
3
+ module Bunny
4
+ # Thread pool that dispatches consumer deliveries. Not supposed to be shared between channels
5
+ # or threads.
6
+ class ConsumerWorkPool
7
+
8
+ #
9
+ # API
10
+ #
11
+
12
+ def initialize(size = 1)
13
+ @size = size
14
+ @queue = ::Queue.new
15
+ end
16
+
17
+
18
+ def submit(callable = nil, &block)
19
+ @queue.push(callable || block)
20
+ end
21
+
22
+ def start
23
+ @threads = []
24
+
25
+ @size.times do
26
+ t = Thread.new(&method(:run_loop))
27
+ @threads << t
28
+ end
29
+
30
+ @started = true
31
+ end
32
+
33
+ def started?
34
+ @started
35
+ end
36
+
37
+ def shutdown
38
+ @size.times do
39
+ submit do |*args|
40
+ throw :terminate
41
+ end
42
+ end
43
+ end
44
+
45
+ def join
46
+ @threads.each { |t| t.join }
47
+ end
48
+
49
+ protected
50
+
51
+ def run_loop
52
+ catch(:terminate) do
53
+ loop do
54
+ callable = @queue.pop
55
+
56
+ begin
57
+ callable.call
58
+ rescue Exception => e
59
+ # TODO
60
+ puts e.class.name
61
+ puts e.message
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,128 @@
1
+ module Bunny
2
+ class TCPConnectionFailed < StandardError
3
+ attr_reader :hostname, :port
4
+
5
+ def initialize(e, hostname, port)
6
+ super("Could not estabilish TCP connection to #{hostname}:#{port}: #{e.message}")
7
+ end
8
+ end
9
+
10
+ class ConnectionClosedError < StandardError
11
+ def initialize(frame)
12
+ if frame.respond_to?(:method_class)
13
+ super("Trying to send frame through a closed connection. Frame is #{frame.inspect}")
14
+ else
15
+ super("Trying to send frame through a closed connection. Frame is #{frame.inspect}, method class is #{frame.method_class}")
16
+ end
17
+ end
18
+ end
19
+
20
+ class PossibleAuthenticationFailureError < StandardError
21
+
22
+ #
23
+ # API
24
+ #
25
+
26
+ attr_reader :username, :vhost
27
+
28
+ def initialize(username, vhost, password_length)
29
+ @username = username
30
+ @vhost = vhost
31
+
32
+ super("AMQP broker closed TCP connection before authentication succeeded: this usually means authentication failure due to misconfiguration or that RabbitMQ version does not support AMQP 0.9.1. Please check your configuration. Username: #{username}, vhost: #{vhost}, password length: #{password_length}")
33
+ end # initialize(settings)
34
+ end # PossibleAuthenticationFailureError
35
+
36
+
37
+ # backwards compatibility
38
+ ConnectionError = TCPConnectionFailed
39
+ ServerDownError = TCPConnectionFailed
40
+
41
+ # TODO
42
+ class ForcedChannelCloseError < StandardError; end
43
+ class ForcedConnectionCloseError < StandardError; end
44
+ class MessageError < StandardError; end
45
+ class ProtocolError < StandardError; end
46
+
47
+ # raised when read or write I/O operations time out (but only if
48
+ # a connection is configured to use them)
49
+ class ClientTimeout < Timeout::Error; end
50
+ # raised on initial connection timeout
51
+ class ConnectionTimeout < Timeout::Error; end
52
+
53
+
54
+ # Base exception class for data consistency and framing errors.
55
+ class InconsistentDataError < StandardError
56
+ end
57
+
58
+ # Raised by adapters when frame does not end with {final octet AMQ::Protocol::Frame::FINAL_OCTET}.
59
+ # This suggest that there is a bug in adapter or AMQ broker implementation.
60
+ #
61
+ # @see http://files.travis-ci.org/docs/amqp/0.9.1/AMQP091Specification.pdf AMQP 0.9.1 specification (Section 2.3)
62
+ class NoFinalOctetError < InconsistentDataError
63
+ def initialize
64
+ super("Frame doesn't end with #{AMQ::Protocol::Frame::FINAL_OCTET} as it must, which means the size is miscalculated.")
65
+ end
66
+ end
67
+
68
+ # Raised by adapters when actual frame payload size in bytes is not equal
69
+ # to the size specified in that frame's header.
70
+ # This suggest that there is a bug in adapter or AMQ broker implementation.
71
+ #
72
+ # @see http://files.travis-ci.org/docs/amqp/0.9.1/AMQP091Specification.pdf AMQP 0.9.1 specification (Section 2.3)
73
+ class BadLengthError < InconsistentDataError
74
+ def initialize(expected_length, actual_length)
75
+ super("Frame payload should be #{expected_length} long, but it's #{actual_length} long.")
76
+ end
77
+ end
78
+
79
+
80
+ class ChannelAlreadyClosed < StandardError
81
+ attr_reader :channel
82
+
83
+ def initialize(message, ch)
84
+ super(message)
85
+
86
+ @channel = ch
87
+ end
88
+ end
89
+
90
+ class ChannelLevelException < StandardError
91
+ attr_reader :channel, :channel_close
92
+
93
+ def initialize(message, ch, channel_close)
94
+ super(message)
95
+
96
+ @channel = ch
97
+ @channel_close = channel_close
98
+ end
99
+ end
100
+
101
+ class PreconditionFailed < ChannelLevelException
102
+ end
103
+
104
+ class NotFound < ChannelLevelException
105
+ end
106
+
107
+ class ResourceLocked < ChannelLevelException
108
+ end
109
+
110
+ class AccessRefused < ChannelLevelException
111
+ end
112
+
113
+
114
+
115
+ class ConnectionLevelException < StandardError
116
+ attr_reader :connection, :connection_close
117
+
118
+ def initialize(message, connection, connection_close)
119
+ super(message)
120
+
121
+ @connection = connection
122
+ @connection_close = connection_close
123
+ end
124
+ end
125
+
126
+ class ChannelError < ConnectionLevelException
127
+ end
128
+ end