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.
- data/.gitignore +7 -1
- data/.travis.yml +14 -4
- data/ChangeLog.md +72 -0
- data/Gemfile +17 -11
- data/README.md +82 -0
- data/bunny.gemspec +6 -13
- data/examples/connection/heartbeat.rb +17 -0
- data/lib/bunny.rb +40 -56
- data/lib/bunny/channel.rb +615 -19
- data/lib/bunny/channel_id_allocator.rb +59 -0
- data/lib/bunny/compatibility.rb +24 -0
- data/lib/bunny/concurrent/condition.rb +63 -0
- data/lib/bunny/consumer.rb +42 -26
- data/lib/bunny/consumer_tag_generator.rb +22 -0
- data/lib/bunny/consumer_work_pool.rb +67 -0
- data/lib/bunny/exceptions.rb +128 -0
- data/lib/bunny/exchange.rb +131 -136
- data/lib/bunny/framing.rb +53 -0
- data/lib/bunny/heartbeat_sender.rb +59 -0
- data/lib/bunny/main_loop.rb +70 -0
- data/lib/bunny/message_metadata.rb +126 -0
- data/lib/bunny/queue.rb +102 -275
- data/lib/bunny/session.rb +478 -0
- data/lib/bunny/socket.rb +44 -0
- data/lib/bunny/system_timer.rb +9 -9
- data/lib/bunny/transport.rb +179 -0
- data/lib/bunny/version.rb +1 -1
- data/spec/compatibility/queue_declare_spec.rb +40 -0
- data/spec/higher_level_api/integration/basic_ack_spec.rb +54 -0
- data/spec/higher_level_api/integration/basic_consume_spec.rb +51 -0
- data/spec/higher_level_api/integration/basic_get_spec.rb +47 -0
- data/spec/higher_level_api/integration/basic_nack_spec.rb +39 -0
- data/spec/higher_level_api/integration/basic_publish_spec.rb +105 -0
- data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -0
- data/spec/higher_level_api/integration/basic_recover_spec.rb +18 -0
- data/spec/higher_level_api/integration/basic_reject_spec.rb +53 -0
- data/spec/higher_level_api/integration/basic_return_spec.rb +33 -0
- data/spec/higher_level_api/integration/channel_close_spec.rb +29 -0
- data/spec/higher_level_api/integration/channel_flow_spec.rb +24 -0
- data/spec/higher_level_api/integration/channel_open_spec.rb +57 -0
- data/spec/higher_level_api/integration/channel_open_stress_spec.rb +22 -0
- data/spec/higher_level_api/integration/confirm_select_spec.rb +19 -0
- data/spec/higher_level_api/integration/connection_spec.rb +340 -0
- data/spec/higher_level_api/integration/exchange_bind_spec.rb +31 -0
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +183 -0
- data/spec/higher_level_api/integration/exchange_delete_spec.rb +37 -0
- data/spec/higher_level_api/integration/exchange_unbind_spec.rb +40 -0
- data/spec/higher_level_api/integration/queue_bind_spec.rb +109 -0
- data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -0
- data/spec/higher_level_api/integration/queue_delete_spec.rb +38 -0
- data/spec/higher_level_api/integration/queue_purge_spec.rb +30 -0
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +33 -0
- data/spec/higher_level_api/integration/tx_commit_spec.rb +21 -0
- data/spec/higher_level_api/integration/tx_rollback_spec.rb +21 -0
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +57 -0
- data/spec/lower_level_api/integration/basic_consume_spec.rb +100 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/unit/bunny_spec.rb +15 -0
- data/spec/unit/concurrent/condition_spec.rb +66 -0
- metadata +135 -93
- data/CHANGELOG +0 -21
- data/README.textile +0 -76
- data/Rakefile +0 -14
- data/examples/simple.rb +0 -32
- data/examples/simple_ack.rb +0 -35
- data/examples/simple_consumer.rb +0 -55
- data/examples/simple_fanout.rb +0 -41
- data/examples/simple_headers.rb +0 -42
- data/examples/simple_publisher.rb +0 -29
- data/examples/simple_topic.rb +0 -61
- data/ext/amqp-0.9.1.json +0 -389
- data/ext/config.yml +0 -4
- data/ext/qparser.rb +0 -426
- data/lib/bunny/client.rb +0 -370
- data/lib/bunny/subscription.rb +0 -92
- data/lib/qrack/amq-client-url.rb +0 -165
- data/lib/qrack/channel.rb +0 -20
- data/lib/qrack/client.rb +0 -247
- data/lib/qrack/errors.rb +0 -5
- data/lib/qrack/protocol/protocol.rb +0 -135
- data/lib/qrack/protocol/spec.rb +0 -525
- data/lib/qrack/qrack.rb +0 -20
- data/lib/qrack/queue.rb +0 -40
- data/lib/qrack/subscription.rb +0 -152
- data/lib/qrack/transport/buffer.rb +0 -305
- data/lib/qrack/transport/frame.rb +0 -102
- data/spec/spec_09/amqp_url_spec.rb +0 -19
- data/spec/spec_09/bunny_spec.rb +0 -76
- data/spec/spec_09/connection_spec.rb +0 -34
- data/spec/spec_09/exchange_spec.rb +0 -173
- 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
|
data/lib/bunny/consumer.rb
CHANGED
@@ -1,35 +1,51 @@
|
|
1
|
-
|
1
|
+
module Bunny
|
2
|
+
class Consumer
|
2
3
|
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
####################################
|
4
|
+
#
|
5
|
+
# API
|
6
|
+
#
|
7
7
|
|
8
|
-
|
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
|
-
|
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
|
-
|
20
|
-
|
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
|
-
|
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
|