bunny 0.8.0 → 0.9.0.pre1

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